유니버CE 초기화 테스트 완료
This commit is contained in:
@@ -1,504 +0,0 @@
|
||||
import React, {
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useRef,
|
||||
useCallback,
|
||||
useState,
|
||||
} from "react";
|
||||
import { useAppStore } from "../../stores/useAppStore";
|
||||
import type { SheetData } from "../../types/sheet";
|
||||
|
||||
// Window 타입 확장
|
||||
declare global {
|
||||
interface Window {
|
||||
luckysheet: any;
|
||||
LuckyExcel: any;
|
||||
$: any; // jQuery
|
||||
Store: any; // Luckysheet Store
|
||||
luckysheet_function: any; // Luckysheet function list
|
||||
functionlist: any[]; // 글로벌 functionlist
|
||||
luckysheetConfigsetting: any; // Luckysheet 설정 객체
|
||||
luckysheetPostil: any; // Luckysheet 포스틸 객체
|
||||
}
|
||||
}
|
||||
|
||||
interface SheetViewerProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Luckysheet 시트 뷰어 컴포넌트
|
||||
* - 메모리 정보 기반: LuckyExcel 변환 결과를 직접 사용
|
||||
* - 커스텀 검증이나 데이터 구조 변경 금지
|
||||
* - luckysheet.create({ data: exportJson.sheets })로 직접 사용
|
||||
*/
|
||||
export function SheetViewer({ className }: SheetViewerProps) {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const luckysheetRef = useRef<any>(null);
|
||||
const [isInitialized, setIsInitialized] = useState(false);
|
||||
const [isConverting, setIsConverting] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [isContainerReady, setIsContainerReady] = useState(false);
|
||||
const [librariesLoaded, setLibrariesLoaded] = useState(false);
|
||||
|
||||
// 스토어에서 현재 파일 정보만 가져오기 (시트 데이터는 LuckyExcel로 직접 변환)
|
||||
const { currentFile, setSelectedRange } = useAppStore();
|
||||
|
||||
/**
|
||||
* CDN 배포판 라이브러리 로딩
|
||||
*/
|
||||
const loadLuckysheetLibrary = useCallback((): Promise<void> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
// 이미 로드된 경우
|
||||
if (
|
||||
window.luckysheet &&
|
||||
window.LuckyExcel &&
|
||||
window.$ &&
|
||||
librariesLoaded
|
||||
) {
|
||||
console.log("📦 모든 라이브러리가 이미 로드됨");
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
const loadResource = (
|
||||
type: "css" | "js",
|
||||
src: string,
|
||||
id: string,
|
||||
): Promise<void> => {
|
||||
return new Promise((resourceResolve, resourceReject) => {
|
||||
// 이미 로드된 리소스 체크
|
||||
if (document.querySelector(`[data-luckysheet-id="${id}"]`)) {
|
||||
resourceResolve();
|
||||
return;
|
||||
}
|
||||
|
||||
if (type === "css") {
|
||||
const link = document.createElement("link");
|
||||
link.rel = "stylesheet";
|
||||
link.href = src;
|
||||
link.setAttribute("data-luckysheet-id", id);
|
||||
link.onload = () => resourceResolve();
|
||||
link.onerror = (error) =>
|
||||
resourceReject(new Error(`${id} CSS 로드 실패`));
|
||||
document.head.appendChild(link);
|
||||
} else {
|
||||
const script = document.createElement("script");
|
||||
script.src = src;
|
||||
script.setAttribute("data-luckysheet-id", id);
|
||||
script.onload = () => resourceResolve();
|
||||
script.onerror = (error) =>
|
||||
resourceReject(new Error(`${id} JS 로드 실패`));
|
||||
document.head.appendChild(script);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const loadSequence = async () => {
|
||||
try {
|
||||
// 1. jQuery (Luckysheet 의존성)
|
||||
if (!window.$) {
|
||||
await loadResource(
|
||||
"js",
|
||||
"https://code.jquery.com/jquery-3.6.0.min.js",
|
||||
"jquery",
|
||||
);
|
||||
}
|
||||
|
||||
// 2. CSS 로드 (공식 문서 순서 준수)
|
||||
await loadResource(
|
||||
"css",
|
||||
"https://cdn.jsdelivr.net/npm/luckysheet/dist/plugins/css/pluginsCss.css",
|
||||
"plugins-css",
|
||||
);
|
||||
await loadResource(
|
||||
"css",
|
||||
"https://cdn.jsdelivr.net/npm/luckysheet/dist/plugins/plugins.css",
|
||||
"plugins-main-css",
|
||||
);
|
||||
await loadResource(
|
||||
"css",
|
||||
"https://cdn.jsdelivr.net/npm/luckysheet/dist/css/luckysheet.css",
|
||||
"luckysheet-css",
|
||||
);
|
||||
await loadResource(
|
||||
"css",
|
||||
"https://cdn.jsdelivr.net/npm/luckysheet/dist/assets/iconfont/iconfont.css",
|
||||
"iconfont-css",
|
||||
);
|
||||
|
||||
// 3. Plugin JS 먼저 로드 (functionlist 초기화)
|
||||
await loadResource(
|
||||
"js",
|
||||
"https://cdn.jsdelivr.net/npm/luckysheet/dist/plugins/js/plugin.js",
|
||||
"plugin-js",
|
||||
);
|
||||
|
||||
// 4. Luckysheet 메인
|
||||
if (!window.luckysheet) {
|
||||
await loadResource(
|
||||
"js",
|
||||
"https://cdn.jsdelivr.net/npm/luckysheet/dist/luckysheet.umd.js",
|
||||
"luckysheet",
|
||||
);
|
||||
}
|
||||
|
||||
// 5. LuckyExcel (Excel 파일 처리용)
|
||||
if (!window.LuckyExcel) {
|
||||
await loadResource(
|
||||
"js",
|
||||
"https://cdn.jsdelivr.net/npm/luckyexcel/dist/luckyexcel.umd.js",
|
||||
"luckyexcel",
|
||||
);
|
||||
}
|
||||
|
||||
// 라이브러리 검증
|
||||
const validationResults = {
|
||||
jquery: !!window.$,
|
||||
luckyExcel: !!window.LuckyExcel,
|
||||
luckysheet: !!window.luckysheet,
|
||||
luckysheetCreate: !!(
|
||||
window.luckysheet &&
|
||||
typeof window.luckysheet.create === "function"
|
||||
),
|
||||
luckysheetDestroy: !!(
|
||||
window.luckysheet &&
|
||||
typeof window.luckysheet.destroy === "function"
|
||||
),
|
||||
};
|
||||
|
||||
if (
|
||||
!validationResults.luckysheet ||
|
||||
!validationResults.luckysheetCreate
|
||||
) {
|
||||
throw new Error(
|
||||
"Luckysheet 객체가 올바르게 초기화되지 않았습니다.",
|
||||
);
|
||||
}
|
||||
|
||||
setLibrariesLoaded(true);
|
||||
console.log("✅ 라이브러리 로드 완료");
|
||||
resolve();
|
||||
} catch (error) {
|
||||
console.error("❌ 라이브러리 로딩 실패:", error);
|
||||
reject(error);
|
||||
}
|
||||
};
|
||||
|
||||
loadSequence();
|
||||
});
|
||||
}, [librariesLoaded]);
|
||||
|
||||
/**
|
||||
* 메모리 정보 기반: LuckyExcel 변환 결과를 직접 사용하는 방식
|
||||
* - LuckyExcel.transformExcelToLucky()에서 반환된 exportJson.sheets를 그대로 사용
|
||||
* - 커스텀 검증이나 데이터 구조 변경 금지
|
||||
*/
|
||||
const convertXLSXWithLuckyExcel = useCallback(
|
||||
async (xlsxBuffer: ArrayBuffer, fileName: string) => {
|
||||
if (!containerRef.current) {
|
||||
console.warn("⚠️ 컨테이너가 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setIsConverting(true);
|
||||
setError(null);
|
||||
|
||||
console.log("🍀 메모리 정보 기반: LuckyExcel 직접 변환 시작...");
|
||||
|
||||
// 라이브러리 로드 확인
|
||||
await loadLuckysheetLibrary();
|
||||
|
||||
// 기존 인스턴스 정리
|
||||
try {
|
||||
if (
|
||||
window.luckysheet &&
|
||||
typeof window.luckysheet.destroy === "function"
|
||||
) {
|
||||
window.luckysheet.destroy();
|
||||
console.log("✅ 기존 인스턴스 destroy 완료");
|
||||
}
|
||||
} catch (destroyError) {
|
||||
console.warn("⚠️ destroy 중 오류 (무시됨):", destroyError);
|
||||
}
|
||||
|
||||
// 컨테이너 초기화
|
||||
if (containerRef.current) {
|
||||
containerRef.current.innerHTML = "";
|
||||
console.log("✅ 컨테이너 초기화 완료");
|
||||
}
|
||||
|
||||
luckysheetRef.current = null;
|
||||
|
||||
console.log("🍀 LuckyExcel.transformExcelToLucky 호출...");
|
||||
|
||||
// ArrayBuffer를 File 객체로 변환 (LuckyExcel은 File 객체 필요)
|
||||
const file = new File([xlsxBuffer], fileName, {
|
||||
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
});
|
||||
|
||||
// LuckyExcel의 직접 변환 사용 (Promise 방식)
|
||||
const luckyExcelResult = await new Promise<any>((resolve, reject) => {
|
||||
try {
|
||||
// 🚨 수정: 첫 번째 매개변수는 File 객체여야 함
|
||||
(window.LuckyExcel as any).transformExcelToLucky(
|
||||
file, // ArrayBuffer 대신 File 객체 사용
|
||||
// 성공 콜백
|
||||
(exportJson: any, luckysheetfile: any) => {
|
||||
console.log("🍀 LuckyExcel 변환 성공!");
|
||||
console.log("🍀 exportJson:", exportJson);
|
||||
console.log("🍀 luckysheetfile:", luckysheetfile);
|
||||
resolve(exportJson);
|
||||
},
|
||||
// 에러 콜백
|
||||
(error: any) => {
|
||||
console.error("❌ LuckyExcel 변환 실패:", error);
|
||||
reject(new Error(`LuckyExcel 변환 실패: ${error}`));
|
||||
},
|
||||
);
|
||||
} catch (callError) {
|
||||
console.error("❌ LuckyExcel 호출 중 오류:", callError);
|
||||
reject(callError);
|
||||
}
|
||||
});
|
||||
|
||||
// 결과 검증
|
||||
if (
|
||||
!luckyExcelResult ||
|
||||
!luckyExcelResult.sheets ||
|
||||
!Array.isArray(luckyExcelResult.sheets)
|
||||
) {
|
||||
throw new Error("LuckyExcel 변환 결과가 유효하지 않습니다.");
|
||||
}
|
||||
|
||||
console.log("🎉 LuckyExcel 변환 완료, Luckysheet 생성 중...");
|
||||
|
||||
// 메모리 정보 기반: exportJson.sheets를 그대로 사용
|
||||
// luckysheet.create({ data: exportJson.sheets })
|
||||
window.luckysheet.create({
|
||||
container: containerRef.current?.id || "luckysheet-container",
|
||||
showinfobar: false,
|
||||
showtoolbar: false,
|
||||
showsheetbar: true,
|
||||
showstatisticBar: false,
|
||||
allowCopy: true,
|
||||
allowEdit: true,
|
||||
// 🚨 핵심: LuckyExcel의 원본 변환 결과를 직접 사용
|
||||
data: luckyExcelResult.sheets, // 가공하지 않고 그대로 전달
|
||||
title: luckyExcelResult.info?.name || fileName,
|
||||
// 🚨 수정: userInfo 경로 수정
|
||||
userInfo: luckyExcelResult.info?.creator || false,
|
||||
});
|
||||
|
||||
console.log("🎉 Luckysheet 생성 완료! (원본 데이터 직접 사용)");
|
||||
setIsInitialized(true);
|
||||
setIsConverting(false);
|
||||
setError(null);
|
||||
luckysheetRef.current = window.luckysheet;
|
||||
} catch (conversionError) {
|
||||
console.error("❌ 변환 프로세스 실패:", conversionError);
|
||||
setError(
|
||||
`변환 프로세스에 실패했습니다: ${
|
||||
conversionError instanceof Error
|
||||
? conversionError.message
|
||||
: String(conversionError)
|
||||
}`,
|
||||
);
|
||||
setIsConverting(false);
|
||||
setIsInitialized(false);
|
||||
}
|
||||
},
|
||||
[loadLuckysheetLibrary, setSelectedRange],
|
||||
);
|
||||
|
||||
/**
|
||||
* DOM 컨테이너 준비 상태 체크 - useLayoutEffect로 동기적 체크
|
||||
*/
|
||||
useLayoutEffect(() => {
|
||||
if (containerRef.current) {
|
||||
console.log("✅ DOM 컨테이너 준비 완료:", containerRef.current.id);
|
||||
setIsContainerReady(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* DOM 컨테이너 준비 상태 재체크 (fallback)
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (!isContainerReady) {
|
||||
const timer = setTimeout(() => {
|
||||
if (containerRef.current && !isContainerReady) {
|
||||
console.log("✅ useEffect: DOM 컨테이너 지연 준비 완료");
|
||||
setIsContainerReady(true);
|
||||
}
|
||||
}, 100);
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [isContainerReady]);
|
||||
|
||||
/**
|
||||
* 컴포넌트 마운트 시 초기화
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (
|
||||
currentFile?.xlsxBuffer &&
|
||||
isContainerReady &&
|
||||
containerRef.current &&
|
||||
!isInitialized &&
|
||||
!isConverting
|
||||
) {
|
||||
console.log("🔄 XLSX 버퍼 감지, LuckyExcel 직접 변환 시작...", {
|
||||
fileName: currentFile.name,
|
||||
bufferSize: currentFile.xlsxBuffer.byteLength,
|
||||
containerId: containerRef.current.id,
|
||||
});
|
||||
|
||||
// 중복 실행 방지
|
||||
setIsConverting(true);
|
||||
|
||||
// LuckyExcel로 직접 변환
|
||||
convertXLSXWithLuckyExcel(currentFile.xlsxBuffer, currentFile.name);
|
||||
} else if (currentFile && !currentFile.xlsxBuffer) {
|
||||
setError("파일 변환 데이터가 없습니다. 파일을 다시 업로드해주세요.");
|
||||
}
|
||||
}, [
|
||||
currentFile?.xlsxBuffer,
|
||||
currentFile?.name,
|
||||
isContainerReady,
|
||||
isInitialized,
|
||||
isConverting,
|
||||
convertXLSXWithLuckyExcel,
|
||||
]);
|
||||
|
||||
/**
|
||||
* 컴포넌트 언마운트 시 정리
|
||||
*/
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (luckysheetRef.current && window.luckysheet) {
|
||||
try {
|
||||
window.luckysheet.destroy();
|
||||
} catch (error) {
|
||||
console.warn("⚠️ Luckysheet 정리 중 오류:", error);
|
||||
}
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* 윈도우 리사이즈 처리
|
||||
*/
|
||||
useEffect(() => {
|
||||
const handleResize = () => {
|
||||
if (luckysheetRef.current && window.luckysheet) {
|
||||
try {
|
||||
window.luckysheet.resize();
|
||||
} catch (error) {
|
||||
console.warn("⚠️ Luckysheet 리사이즈 중 오류:", error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("resize", handleResize);
|
||||
return () => window.removeEventListener("resize", handleResize);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`w-full h-full min-h-[70vh] ${className || ""}`}
|
||||
style={{ position: "relative" }}
|
||||
>
|
||||
{/* Luckysheet 컨테이너 - 항상 렌더링 */}
|
||||
<div
|
||||
ref={containerRef}
|
||||
id="luckysheet-container"
|
||||
className="w-full h-full"
|
||||
style={{
|
||||
minHeight: "70vh",
|
||||
border: "1px solid #e5e7eb",
|
||||
borderRadius: "8px",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* 에러 상태 오버레이 */}
|
||||
{error && (
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-red-50 border border-red-200 rounded-lg">
|
||||
<div className="text-center p-6">
|
||||
<div className="text-red-600 text-lg font-semibold mb-2">
|
||||
시트 로드 오류
|
||||
</div>
|
||||
<div className="text-red-500 text-sm mb-4">{error}</div>
|
||||
<button
|
||||
onClick={() => {
|
||||
setError(null);
|
||||
setIsInitialized(false);
|
||||
setIsConverting(false);
|
||||
if (currentFile?.xlsxBuffer) {
|
||||
convertXLSXWithLuckyExcel(
|
||||
currentFile.xlsxBuffer,
|
||||
currentFile.name,
|
||||
);
|
||||
}
|
||||
}}
|
||||
className="mt-4 px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700 transition-colors"
|
||||
>
|
||||
다시 시도
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 로딩 상태 오버레이 */}
|
||||
{!error &&
|
||||
(isConverting || !isInitialized) &&
|
||||
currentFile?.xlsxBuffer && (
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-blue-50 border border-blue-200 rounded-lg">
|
||||
<div className="text-center p-6">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div>
|
||||
<div className="text-blue-600 text-lg font-semibold mb-2">
|
||||
{isConverting ? "LuckyExcel 변환 중..." : "시트 초기화 중..."}
|
||||
</div>
|
||||
<div className="text-blue-500 text-sm">
|
||||
{isConverting
|
||||
? "원본 Excel 데이터를 완전한 스타일로 변환하고 있습니다."
|
||||
: "잠시만 기다려주세요."}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 데이터 없음 상태 오버레이 */}
|
||||
{!error && !currentFile?.xlsxBuffer && (
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-gray-50 border border-gray-200 rounded-lg">
|
||||
<div className="text-center p-6">
|
||||
<div className="text-gray-500 text-lg font-semibold mb-2">
|
||||
표시할 시트가 없습니다
|
||||
</div>
|
||||
<div className="text-gray-400 text-sm">
|
||||
Excel 파일을 업로드해주세요.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 시트 정보 표시 (개발용) */}
|
||||
{process.env.NODE_ENV === "development" && (
|
||||
<div className="absolute top-2 right-2 bg-black bg-opacity-75 text-white text-xs p-2 rounded z-10">
|
||||
<div>파일: {currentFile?.name}</div>
|
||||
<div>
|
||||
XLSX 버퍼:{" "}
|
||||
{currentFile?.xlsxBuffer
|
||||
? `${currentFile.xlsxBuffer.byteLength} bytes`
|
||||
: "없음"}
|
||||
</div>
|
||||
<div>변환 중: {isConverting ? "예" : "아니오"}</div>
|
||||
<div>초기화: {isInitialized ? "완료" : "대기"}</div>
|
||||
<div>컨테이너 준비: {isContainerReady ? "완료" : "대기"}</div>
|
||||
<div>방식: LuckyExcel 직접 변환</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, {
|
||||
import {
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useRef,
|
||||
@@ -6,21 +6,6 @@ import React, {
|
||||
useState,
|
||||
} from "react";
|
||||
import { useAppStore } from "../../stores/useAppStore";
|
||||
import type { SheetData } from "../../types/sheet";
|
||||
|
||||
// Window 타입 확장
|
||||
declare global {
|
||||
interface Window {
|
||||
luckysheet: any;
|
||||
LuckyExcel: any;
|
||||
$: any; // jQuery
|
||||
Store: any; // Luckysheet Store
|
||||
luckysheet_function: any; // Luckysheet function list
|
||||
functionlist: any[]; // 글로벌 functionlist
|
||||
luckysheetConfigsetting: any; // Luckysheet 설정 객체
|
||||
luckysheetPostil: any; // Luckysheet 포스틸 객체
|
||||
}
|
||||
}
|
||||
|
||||
interface SheetViewerProps {
|
||||
className?: string;
|
||||
@@ -28,9 +13,9 @@ interface SheetViewerProps {
|
||||
|
||||
/**
|
||||
* Luckysheet 시트 뷰어 컴포넌트
|
||||
* - 참고 내용 기반: 완전한 라이브러리 로딩 순서 적용
|
||||
* - functionlist 오류 방지를 위한 완전한 초기화
|
||||
* - 필수 플러그인과 CSS 포함
|
||||
* - 메모리 정보 기반: LuckyExcel 변환 결과를 직접 사용
|
||||
* - 커스텀 검증이나 데이터 구조 변경 금지
|
||||
* - luckysheet.create({ data: exportJson.sheets })로 직접 사용
|
||||
*/
|
||||
export function SheetViewer({ className }: SheetViewerProps) {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
@@ -41,12 +26,11 @@ export function SheetViewer({ className }: SheetViewerProps) {
|
||||
const [isContainerReady, setIsContainerReady] = useState(false);
|
||||
const [librariesLoaded, setLibrariesLoaded] = useState(false);
|
||||
|
||||
// 스토어에서 시트 데이터 가져오기
|
||||
const { sheets, activeSheetId, currentFile, setSelectedRange } =
|
||||
useAppStore();
|
||||
// 스토어에서 현재 파일 정보만 가져오기 (시트 데이터는 LuckyExcel로 직접 변환)
|
||||
const { currentFile, setSelectedRange } = useAppStore();
|
||||
|
||||
/**
|
||||
* CDN 배포판 + functionlist 직접 초기화 방식
|
||||
* CDN 배포판 라이브러리 로딩
|
||||
*/
|
||||
const loadLuckysheetLibrary = useCallback((): Promise<void> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -62,8 +46,6 @@ export function SheetViewer({ className }: SheetViewerProps) {
|
||||
return;
|
||||
}
|
||||
|
||||
// console.log("📦 CDN 배포판 + functionlist 직접 초기화 방식...");
|
||||
|
||||
const loadResource = (
|
||||
type: "css" | "js",
|
||||
src: string,
|
||||
@@ -72,7 +54,6 @@ export function SheetViewer({ className }: SheetViewerProps) {
|
||||
return new Promise((resourceResolve, resourceReject) => {
|
||||
// 이미 로드된 리소스 체크
|
||||
if (document.querySelector(`[data-luckysheet-id="${id}"]`)) {
|
||||
// console.log(`📦 ${id} 이미 로드됨`);
|
||||
resourceResolve();
|
||||
return;
|
||||
}
|
||||
@@ -82,33 +63,22 @@ export function SheetViewer({ className }: SheetViewerProps) {
|
||||
link.rel = "stylesheet";
|
||||
link.href = src;
|
||||
link.setAttribute("data-luckysheet-id", id);
|
||||
link.onload = () => {
|
||||
// console.log(`✅ ${id} CSS 로드 완료`);
|
||||
resourceResolve();
|
||||
};
|
||||
link.onerror = (error) => {
|
||||
// console.error(`❌ ${id} CSS 로드 실패:`, error);
|
||||
link.onload = () => resourceResolve();
|
||||
link.onerror = () =>
|
||||
resourceReject(new Error(`${id} CSS 로드 실패`));
|
||||
};
|
||||
document.head.appendChild(link);
|
||||
} else {
|
||||
const script = document.createElement("script");
|
||||
script.src = src;
|
||||
script.setAttribute("data-luckysheet-id", id);
|
||||
script.onload = () => {
|
||||
// console.log(`✅ ${id} JS 로드 완료`);
|
||||
resourceResolve();
|
||||
};
|
||||
script.onerror = (error) => {
|
||||
// console.error(`❌ ${id} JS 로드 실패:`, error);
|
||||
script.onload = () => resourceResolve();
|
||||
script.onerror = () =>
|
||||
resourceReject(new Error(`${id} JS 로드 실패`));
|
||||
};
|
||||
document.head.appendChild(script);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// CDN 배포판 로딩 + functionlist 직접 초기화
|
||||
const loadSequence = async () => {
|
||||
try {
|
||||
// 1. jQuery (Luckysheet 의존성)
|
||||
@@ -120,79 +90,54 @@ export function SheetViewer({ className }: SheetViewerProps) {
|
||||
);
|
||||
}
|
||||
|
||||
// 2. CSS 로드 (빌드된 파일들)
|
||||
// 2. CSS 로드 (공식 문서 순서 준수)
|
||||
await loadResource(
|
||||
"css",
|
||||
"/luckysheet/dist/plugins/css/pluginsCss.css",
|
||||
"https://cdn.jsdelivr.net/npm/luckysheet/dist/plugins/css/pluginsCss.css",
|
||||
"plugins-css",
|
||||
);
|
||||
await loadResource(
|
||||
"css",
|
||||
"/luckysheet/dist/plugins/plugins.css",
|
||||
"https://cdn.jsdelivr.net/npm/luckysheet/dist/plugins/plugins.css",
|
||||
"plugins-main-css",
|
||||
);
|
||||
await loadResource(
|
||||
"css",
|
||||
"/luckysheet/dist/css/luckysheet.css",
|
||||
"https://cdn.jsdelivr.net/npm/luckysheet/dist/css/luckysheet.css",
|
||||
"luckysheet-css",
|
||||
);
|
||||
await loadResource(
|
||||
"css",
|
||||
"/luckysheet/dist/assets/iconfont/iconfont.css",
|
||||
"https://cdn.jsdelivr.net/npm/luckysheet/dist/assets/iconfont/iconfont.css",
|
||||
"iconfont-css",
|
||||
);
|
||||
|
||||
// 3. Plugin JS 먼저 로드 (functionlist 초기화 우선)
|
||||
// 3. Plugin JS 먼저 로드 (functionlist 초기화)
|
||||
await loadResource(
|
||||
"js",
|
||||
"/luckysheet/dist/plugins/js/plugin.js",
|
||||
"https://cdn.jsdelivr.net/npm/luckysheet/dist/plugins/js/plugin.js",
|
||||
"plugin-js",
|
||||
);
|
||||
|
||||
// 👉 plugin.js 로드 후 실제 functionlist 가 채워졌는지 polling 으로 확인 (최대 3초)
|
||||
const waitForFunctionlistReady = (
|
||||
timeout = 3000,
|
||||
interval = 50,
|
||||
): Promise<void> => {
|
||||
return new Promise((res, rej) => {
|
||||
let waited = 0;
|
||||
const timer = setInterval(() => {
|
||||
if (window.Store?.functionlist?.length) {
|
||||
clearInterval(timer);
|
||||
res();
|
||||
} else if ((waited += interval) >= timeout) {
|
||||
clearInterval(timer);
|
||||
rej(new Error("functionlist 초기화 시간 초과"));
|
||||
}
|
||||
}, interval);
|
||||
});
|
||||
};
|
||||
|
||||
await waitForFunctionlistReady();
|
||||
|
||||
// 4. LuckyExcel (Excel 파일 처리용)
|
||||
if (!window.LuckyExcel) {
|
||||
await loadResource(
|
||||
"js",
|
||||
"/luckysheet/dist/luckyexcel.umd.js",
|
||||
"luckyexcel",
|
||||
);
|
||||
}
|
||||
|
||||
// 5. Luckysheet 메인 (functionlist 준비 후)
|
||||
// 4. Luckysheet 메인
|
||||
if (!window.luckysheet) {
|
||||
await loadResource(
|
||||
"js",
|
||||
"/luckysheet/dist/luckysheet.umd.js",
|
||||
"https://cdn.jsdelivr.net/npm/luckysheet/dist/luckysheet.umd.js",
|
||||
"luckysheet",
|
||||
);
|
||||
}
|
||||
|
||||
// 라이브러리 로드 후 검증
|
||||
// console.log("🔍 라이브러리 로드 후 검증 중...");
|
||||
// 5. LuckyExcel (Excel 파일 처리용)
|
||||
if (!window.LuckyExcel) {
|
||||
await loadResource(
|
||||
"js",
|
||||
"https://cdn.jsdelivr.net/npm/luckyexcel/dist/luckyexcel.umd.js",
|
||||
"luckyexcel",
|
||||
);
|
||||
}
|
||||
|
||||
// NOTE: plugin.js 가 실제 functionlist 를 채웠으므로 별도 지연 대기 불필요
|
||||
// 필수 객체 검증
|
||||
// 라이브러리 검증
|
||||
const validationResults = {
|
||||
jquery: !!window.$,
|
||||
luckyExcel: !!window.LuckyExcel,
|
||||
@@ -207,8 +152,6 @@ export function SheetViewer({ className }: SheetViewerProps) {
|
||||
),
|
||||
};
|
||||
|
||||
// console.log("🔍 라이브러리 검증 결과:", validationResults);
|
||||
|
||||
if (
|
||||
!validationResults.luckysheet ||
|
||||
!validationResults.luckysheetCreate
|
||||
@@ -218,75 +161,11 @@ export function SheetViewer({ className }: SheetViewerProps) {
|
||||
);
|
||||
}
|
||||
|
||||
// 🔧 강력한 functionlist 초기화 (메모리 해결책 적용)
|
||||
// console.log("🔧 강력한 functionlist 및 모든 필수 객체 초기화 중...");
|
||||
try {
|
||||
// 1. Store 객체 강제 생성
|
||||
if (!window.Store) {
|
||||
window.Store = {};
|
||||
}
|
||||
|
||||
// 2. functionlist 다중 레벨 초기화
|
||||
if (!window.Store.functionlist) {
|
||||
window.Store.functionlist = [];
|
||||
}
|
||||
|
||||
// 3. luckysheet_function 다중 레벨 초기화
|
||||
if (!window.luckysheet_function) {
|
||||
window.luckysheet_function = {};
|
||||
}
|
||||
if (!window.Store.luckysheet_function) {
|
||||
window.Store.luckysheet_function = {};
|
||||
}
|
||||
|
||||
// 4. Luckysheet 내부에서 사용하는 추가 functionlist 객체들 초기화
|
||||
if (window.luckysheet && !window.luckysheet.functionlist) {
|
||||
window.luckysheet.functionlist = [];
|
||||
}
|
||||
|
||||
// 5. 글로벌 functionlist 초기화 (다양한 참조 경로 대응)
|
||||
if (!window.functionlist) {
|
||||
window.functionlist = [];
|
||||
}
|
||||
|
||||
// 6. Store 내부 구조 완전 초기화
|
||||
if (!window.Store.config) {
|
||||
window.Store.config = {};
|
||||
}
|
||||
if (!window.Store.luckysheetfile) {
|
||||
window.Store.luckysheetfile = [];
|
||||
}
|
||||
if (!window.Store.currentSheetIndex) {
|
||||
window.Store.currentSheetIndex = 0;
|
||||
}
|
||||
|
||||
// 7. Luckysheet 모듈별 초기화 확인
|
||||
if (window.luckysheet) {
|
||||
// 함수 관련 모듈 초기화
|
||||
if (!window.luckysheet.formula) {
|
||||
window.luckysheet.formula = {};
|
||||
}
|
||||
if (!window.luckysheet.formulaCache) {
|
||||
window.luckysheet.formulaCache = {};
|
||||
}
|
||||
if (!window.luckysheet.formulaObjects) {
|
||||
window.luckysheet.formulaObjects = {};
|
||||
}
|
||||
}
|
||||
|
||||
// console.log("✅ 강력한 functionlist 및 모든 필수 객체 초기화 완료");
|
||||
} catch (functionError) {
|
||||
// console.warn(
|
||||
// "⚠️ 강력한 functionlist 초기화 중 오류 (무시됨):",
|
||||
// functionError,
|
||||
// );
|
||||
}
|
||||
|
||||
setLibrariesLoaded(true);
|
||||
// console.log("✅ CDN 배포판 + functionlist 초기화 완료");
|
||||
console.log("✅ 라이브러리 로드 완료");
|
||||
resolve();
|
||||
} catch (error) {
|
||||
// console.error("❌ 라이브러리 로딩 실패:", error);
|
||||
console.error("❌ 라이브러리 로딩 실패:", error);
|
||||
reject(error);
|
||||
}
|
||||
};
|
||||
@@ -296,9 +175,11 @@ export function SheetViewer({ className }: SheetViewerProps) {
|
||||
}, [librariesLoaded]);
|
||||
|
||||
/**
|
||||
* 참고 내용 기반: 올바른 데이터 구조로 Luckysheet 초기화
|
||||
* 메모리 정보 기반: LuckyExcel 변환 결과를 직접 사용하는 방식
|
||||
* - LuckyExcel.transformExcelToLucky()에서 반환된 exportJson.sheets를 그대로 사용
|
||||
* - 커스텀 검증이나 데이터 구조 변경 금지
|
||||
*/
|
||||
const convertXLSXToLuckysheet = useCallback(
|
||||
const convertXLSXWithLuckyExcel = useCallback(
|
||||
async (xlsxBuffer: ArrayBuffer, fileName: string) => {
|
||||
if (!containerRef.current) {
|
||||
console.warn("⚠️ 컨테이너가 없습니다.");
|
||||
@@ -309,317 +190,97 @@ export function SheetViewer({ className }: SheetViewerProps) {
|
||||
setIsConverting(true);
|
||||
setError(null);
|
||||
|
||||
// console.log(
|
||||
// "🔄 참고 내용 기반: XLSX → LuckyExcel → Luckysheet 변환 시작...",
|
||||
// );
|
||||
console.log("🍀 메모리 정보 기반: LuckyExcel 직접 변환 시작...");
|
||||
|
||||
// 라이브러리 로드 확인
|
||||
await loadLuckysheetLibrary();
|
||||
|
||||
// 기존 인스턴스 정리 (참고 내용 권장사항)
|
||||
// console.log("🧹 기존 Luckysheet 인스턴스 정리...");
|
||||
// 기존 인스턴스 정리
|
||||
try {
|
||||
if (
|
||||
window.luckysheet &&
|
||||
typeof window.luckysheet.destroy === "function"
|
||||
) {
|
||||
window.luckysheet.destroy();
|
||||
// console.log("✅ 기존 인스턴스 destroy 완료");
|
||||
console.log("✅ 기존 인스턴스 destroy 완료");
|
||||
}
|
||||
} catch (destroyError) {
|
||||
// console.warn("⚠️ destroy 중 오류 (무시됨):", destroyError);
|
||||
console.warn("⚠️ destroy 중 오류 (무시됨):", destroyError);
|
||||
}
|
||||
|
||||
// 컨테이너 초기화
|
||||
if (containerRef.current) {
|
||||
containerRef.current.innerHTML = "";
|
||||
// console.log("✅ 컨테이너 초기화 완료");
|
||||
console.log("✅ 컨테이너 초기화 완료");
|
||||
}
|
||||
|
||||
luckysheetRef.current = null;
|
||||
|
||||
// console.log("🍀 LuckyExcel.transformExcelToLucky 호출...");
|
||||
console.log("🍀 LuckyExcel.transformExcelToLucky 호출...");
|
||||
|
||||
// LuckyExcel 변환 (참고 내용의 블로그 포스트 방식)
|
||||
window.LuckyExcel.transformExcelToLucky(
|
||||
xlsxBuffer,
|
||||
// 성공 콜백 - 변환 완료 후에만 Luckysheet 초기화
|
||||
(exportJson: any, luckysheetfile: any) => {
|
||||
try {
|
||||
// console.log("✅ LuckyExcel 변환 완료:", {
|
||||
// hasExportJson: !!exportJson,
|
||||
// hasSheets: !!exportJson?.sheets,
|
||||
// sheetsCount: exportJson?.sheets?.length || 0,
|
||||
// sheetsStructure:
|
||||
// exportJson?.sheets?.map((sheet: any, index: number) => ({
|
||||
// index,
|
||||
// name: sheet?.name,
|
||||
// hasData: !!sheet?.data,
|
||||
// dataLength: Array.isArray(sheet?.data)
|
||||
// ? sheet.data.length
|
||||
// : 0,
|
||||
// })) || [],
|
||||
// });
|
||||
// ArrayBuffer를 File 객체로 변환 (LuckyExcel은 File 객체 필요)
|
||||
const file = new File([xlsxBuffer], fileName, {
|
||||
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
});
|
||||
|
||||
// 공식 LuckyExcel 방식: 기본 검증만 수행 (과도한 변환 방지)
|
||||
if (
|
||||
!exportJson ||
|
||||
!exportJson.sheets ||
|
||||
!Array.isArray(exportJson.sheets)
|
||||
) {
|
||||
throw new Error(
|
||||
"LuckyExcel 변환 결과가 유효하지 않습니다: sheets 배열이 없음",
|
||||
);
|
||||
}
|
||||
|
||||
if (exportJson.sheets.length === 0) {
|
||||
throw new Error("변환된 시트가 없습니다.");
|
||||
}
|
||||
|
||||
// console.log("✅ LuckyExcel 변환 결과 검증 완료:", {
|
||||
// sheetsCount: exportJson.sheets.length,
|
||||
// hasInfo: !!exportJson.info,
|
||||
// infoName: exportJson.info?.name,
|
||||
// infoCreator: exportJson.info?.creator,
|
||||
// });
|
||||
|
||||
// console.log(
|
||||
// "🎯 functionlist 초기화 완료: Luckysheet 초기화 시작...",
|
||||
// );
|
||||
|
||||
// 메모리 해결책: 최종 완전한 functionlist 및 모든 Luckysheet 내부 객체 초기화
|
||||
try {
|
||||
// Level 1: Store 객체 완전 초기화
|
||||
if (!window.Store) window.Store = {};
|
||||
if (!window.Store.functionlist) window.Store.functionlist = [];
|
||||
if (!window.Store.luckysheet_function)
|
||||
window.Store.luckysheet_function = {};
|
||||
if (!window.Store.config) window.Store.config = {};
|
||||
if (!window.Store.luckysheetfile)
|
||||
window.Store.luckysheetfile = [];
|
||||
|
||||
// Level 2: 글로벌 function 객체들 완전 초기화
|
||||
if (!window.luckysheet_function)
|
||||
window.luckysheet_function = {};
|
||||
if (!window.functionlist) window.functionlist = [];
|
||||
|
||||
// Level 3: Luckysheet 내부 깊은 레벨 초기화
|
||||
if (window.luckysheet) {
|
||||
// 함수 관련 깊은 객체들
|
||||
if (!window.luckysheet.functionlist)
|
||||
window.luckysheet.functionlist = [];
|
||||
if (!window.luckysheet.formula)
|
||||
window.luckysheet.formula = {};
|
||||
if (!window.luckysheet.formulaCache)
|
||||
window.luckysheet.formulaCache = {};
|
||||
if (!window.luckysheet.formulaObjects)
|
||||
window.luckysheet.formulaObjects = {};
|
||||
|
||||
// Store 레퍼런스 초기화
|
||||
if (!window.luckysheet.Store)
|
||||
window.luckysheet.Store = window.Store;
|
||||
if (!window.luckysheet.luckysheetfile)
|
||||
window.luckysheet.luckysheetfile = [];
|
||||
|
||||
// 내부 모듈들 초기화
|
||||
if (!window.luckysheet.menuButton)
|
||||
window.luckysheet.menuButton = {};
|
||||
if (!window.luckysheet.server) window.luckysheet.server = {};
|
||||
if (!window.luckysheet.selection)
|
||||
window.luckysheet.selection = {};
|
||||
}
|
||||
|
||||
// Level 4: 추가적인 깊은 레벨 객체들 (Luckysheet 내부에서 사용할 수 있는)
|
||||
if (!window.luckysheetConfigsetting)
|
||||
window.luckysheetConfigsetting = {};
|
||||
if (!window.luckysheetPostil) window.luckysheetPostil = {};
|
||||
if (!window.Store.visibledatarow)
|
||||
window.Store.visibledatarow = [];
|
||||
if (!window.Store.visibledatacolumn)
|
||||
window.Store.visibledatacolumn = [];
|
||||
if (!window.Store.defaultcollen)
|
||||
window.Store.defaultcollen = 73;
|
||||
if (!window.Store.defaultrowlen)
|
||||
window.Store.defaultrowlen = 19;
|
||||
|
||||
console.log("✅ 완전한 Luckysheet 내부 객체 초기화 완료");
|
||||
|
||||
// 극한의 방법: Luckysheet 내부 코드 직접 패치 (임시)
|
||||
if (
|
||||
window.luckysheet &&
|
||||
typeof window.luckysheet.create === "function"
|
||||
) {
|
||||
// Luckysheet 내부에서 사용하는 모든 가능한 functionlist 경로 강제 생성
|
||||
const originalCreate = window.luckysheet.create;
|
||||
window.luckysheet.create = function (options: any) {
|
||||
try {
|
||||
// 생성 직전 모든 functionlist 경로 재검증
|
||||
if (!window.Store) window.Store = {};
|
||||
if (!window.Store.functionlist)
|
||||
window.Store.functionlist = [];
|
||||
if (!this.functionlist) this.functionlist = [];
|
||||
if (!this.Store) this.Store = window.Store;
|
||||
if (!this.Store.functionlist)
|
||||
this.Store.functionlist = [];
|
||||
|
||||
// 원본 함수 호출
|
||||
return originalCreate.call(this, options);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"Luckysheet create 패치된 함수에서 오류:",
|
||||
error,
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
console.log("🔧 Luckysheet.create 함수 패치 완료");
|
||||
}
|
||||
} catch (finalInitError) {
|
||||
console.warn(
|
||||
"⚠️ 완전한 functionlist 초기화 중 오류:",
|
||||
finalInitError,
|
||||
);
|
||||
}
|
||||
|
||||
// 공식 LuckyExcel 방식: exportJson.sheets를 직접 사용
|
||||
const luckysheetOptions = {
|
||||
container: containerRef.current?.id || "luckysheet-container",
|
||||
title: exportJson.info?.name || fileName || "Sheet Easy AI",
|
||||
lang: "ko",
|
||||
data: exportJson.sheets, // 공식 방식: LuckyExcel 결과를 직접 사용
|
||||
|
||||
// userInfo도 공식 예시대로 설정
|
||||
userInfo: exportJson.info?.creator || "Sheet Easy AI User",
|
||||
|
||||
// UI 설정 (모든 기능 활성화)
|
||||
showinfobar: false,
|
||||
showtoolbar: true, // 툴바 활성화
|
||||
showsheetbar: true,
|
||||
showstatisticBar: false,
|
||||
showConfigWindowResize: true,
|
||||
|
||||
// 편집 기능 활성화
|
||||
allowCopy: true,
|
||||
allowEdit: true,
|
||||
enableAddRow: true, // 행/열 추가 활성화
|
||||
enableAddCol: true,
|
||||
|
||||
// 함수 기능 활성화
|
||||
allowUpdate: true,
|
||||
enableAddBackTop: true,
|
||||
showFormulaBar: true, // 수식바 활성화
|
||||
|
||||
// 이벤트 핸들러 (모든 기능)
|
||||
hook: {
|
||||
cellClick: (cell: any, position: any, sheetFile: any) => {
|
||||
// console.log("🖱️ 셀 클릭:", { cell, position, sheetFile });
|
||||
if (
|
||||
position &&
|
||||
typeof position.r === "number" &&
|
||||
typeof position.c === "number"
|
||||
) {
|
||||
setSelectedRange({
|
||||
range: {
|
||||
startRow: position.r,
|
||||
startCol: position.c,
|
||||
endRow: position.r,
|
||||
endCol: position.c,
|
||||
},
|
||||
sheetId: sheetFile?.index || "0",
|
||||
});
|
||||
}
|
||||
},
|
||||
sheetActivate: (
|
||||
index: number,
|
||||
isPivotInitial: boolean,
|
||||
isInitialLoad: boolean,
|
||||
) => {
|
||||
// console.log("📋 시트 활성화:", {
|
||||
// index,
|
||||
// isPivotInitial,
|
||||
// isInitialLoad,
|
||||
// });
|
||||
if (exportJson.sheets[index]) {
|
||||
useAppStore.getState().setActiveSheetId(`sheet_${index}`);
|
||||
}
|
||||
},
|
||||
updated: (operate: any) => {
|
||||
// console.log("🔄 시트 업데이트:", operate);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// === 상세 디버깅 정보 ===
|
||||
console.log("=== LuckyExcel → Luckysheet 디버깅 정보 ===");
|
||||
console.log("📋 exportJson 구조:", {
|
||||
hasExportJson: !!exportJson,
|
||||
hasInfo: !!exportJson?.info,
|
||||
hasSheets: !!exportJson?.sheets,
|
||||
sheetsCount: exportJson?.sheets?.length,
|
||||
infoKeys: Object.keys(exportJson?.info || {}),
|
||||
firstSheetKeys: Object.keys(exportJson?.sheets?.[0] || {}),
|
||||
});
|
||||
console.log("🔧 Functionlist 상태 상세 검사:", {
|
||||
windowStore: !!window.Store,
|
||||
storeFunctionlist: !!window.Store?.functionlist,
|
||||
storeFunctionlistLength: window.Store?.functionlist?.length,
|
||||
luckysheetFunction: !!window.luckysheet_function,
|
||||
globalFunctionlist: !!window.functionlist,
|
||||
globalFunctionlistLength: window.functionlist?.length,
|
||||
luckysheetStoreFunctionlist:
|
||||
!!window.luckysheet?.Store?.functionlist,
|
||||
luckysheetOwnFunctionlist: !!window.luckysheet?.functionlist,
|
||||
allWindowKeys: Object.keys(window).filter((key) =>
|
||||
key.includes("function"),
|
||||
),
|
||||
allStoreKeys: Object.keys(window.Store || {}),
|
||||
allLuckysheetKeys: Object.keys(window.luckysheet || {}).slice(
|
||||
0,
|
||||
10,
|
||||
),
|
||||
});
|
||||
console.log("🎯 Luckysheet 객체:", {
|
||||
hasLuckysheet: !!window.luckysheet,
|
||||
hasCreate: typeof window.luckysheet?.create === "function",
|
||||
methodsCount: Object.keys(window.luckysheet || {}).length,
|
||||
});
|
||||
console.log("=== 디버깅 정보 끝 ===");
|
||||
|
||||
// Luckysheet 생성
|
||||
window.luckysheet.create(luckysheetOptions);
|
||||
|
||||
luckysheetRef.current = window.luckysheet;
|
||||
setIsInitialized(true);
|
||||
setIsConverting(false);
|
||||
|
||||
// console.log(
|
||||
// "✅ functionlist 초기화 완료: Luckysheet 초기화 완료!",
|
||||
// );
|
||||
} catch (initError) {
|
||||
console.error("❌ Luckysheet 초기화 실패:", initError);
|
||||
setError(
|
||||
`시트 초기화에 실패했습니다: ${
|
||||
initError instanceof Error
|
||||
? initError.message
|
||||
: String(initError)
|
||||
}`,
|
||||
);
|
||||
setIsInitialized(false);
|
||||
setIsConverting(false);
|
||||
}
|
||||
},
|
||||
// 오류 콜백
|
||||
(error: any) => {
|
||||
console.error("❌ LuckyExcel 변환 실패:", error);
|
||||
setError(
|
||||
`XLSX 변환에 실패했습니다: ${
|
||||
error instanceof Error ? error.message : String(error)
|
||||
}`,
|
||||
// LuckyExcel의 직접 변환 사용 (Promise 방식)
|
||||
const luckyExcelResult = await new Promise<any>((resolve, reject) => {
|
||||
try {
|
||||
// 🚨 수정: 첫 번째 매개변수는 File 객체여야 함
|
||||
(window.LuckyExcel as any).transformExcelToLucky(
|
||||
file, // ArrayBuffer 대신 File 객체 사용
|
||||
// 성공 콜백
|
||||
(exportJson: any, luckysheetfile: any) => {
|
||||
console.log("🍀 LuckyExcel 변환 성공!");
|
||||
console.log("🍀 exportJson:", exportJson);
|
||||
console.log("🍀 luckysheetfile:", luckysheetfile);
|
||||
resolve(exportJson);
|
||||
},
|
||||
// 에러 콜백
|
||||
(error: any) => {
|
||||
console.error("❌ LuckyExcel 변환 실패:", error);
|
||||
reject(new Error(`LuckyExcel 변환 실패: ${error}`));
|
||||
},
|
||||
);
|
||||
setIsConverting(false);
|
||||
setIsInitialized(false);
|
||||
},
|
||||
);
|
||||
} catch (callError) {
|
||||
console.error("❌ LuckyExcel 호출 중 오류:", callError);
|
||||
reject(callError);
|
||||
}
|
||||
});
|
||||
|
||||
// 결과 검증
|
||||
if (
|
||||
!luckyExcelResult ||
|
||||
!luckyExcelResult.sheets ||
|
||||
!Array.isArray(luckyExcelResult.sheets)
|
||||
) {
|
||||
throw new Error("LuckyExcel 변환 결과가 유효하지 않습니다.");
|
||||
}
|
||||
|
||||
console.log("🎉 LuckyExcel 변환 완료, Luckysheet 생성 중...");
|
||||
|
||||
// 메모리 정보 기반: exportJson.sheets를 그대로 사용
|
||||
// luckysheet.create({ data: exportJson.sheets })
|
||||
window.luckysheet.create({
|
||||
container: containerRef.current?.id || "luckysheet-container",
|
||||
showinfobar: true,
|
||||
showtoolbar: true,
|
||||
showsheetbar: true,
|
||||
showstatisticBar: true,
|
||||
allowCopy: true,
|
||||
allowEdit: true,
|
||||
// 🚨 핵심: LuckyExcel의 원본 변환 결과를 직접 사용
|
||||
data: luckyExcelResult.sheets, // 가공하지 않고 그대로 전달
|
||||
title: luckyExcelResult.info?.name || fileName,
|
||||
// 🚨 수정: userInfo 경로 수정
|
||||
userInfo: luckyExcelResult.info?.creator || false,
|
||||
});
|
||||
|
||||
console.log("🎉 Luckysheet 생성 완료! (원본 데이터 직접 사용)");
|
||||
setIsInitialized(true);
|
||||
setIsConverting(false);
|
||||
setError(null);
|
||||
luckysheetRef.current = window.luckysheet;
|
||||
} catch (conversionError) {
|
||||
console.error("❌ 변환 프로세스 실패:", conversionError);
|
||||
setError(
|
||||
@@ -640,12 +301,9 @@ export function SheetViewer({ className }: SheetViewerProps) {
|
||||
* DOM 컨테이너 준비 상태 체크 - useLayoutEffect로 동기적 체크
|
||||
*/
|
||||
useLayoutEffect(() => {
|
||||
// console.log("🔍 useLayoutEffect: DOM 컨테이너 체크 시작...");
|
||||
if (containerRef.current) {
|
||||
// console.log("✅ DOM 컨테이너 준비 완료:", containerRef.current.id);
|
||||
console.log("✅ DOM 컨테이너 준비 완료:", containerRef.current.id);
|
||||
setIsContainerReady(true);
|
||||
} else {
|
||||
// console.warn("⚠️ useLayoutEffect: DOM 컨테이너가 아직 준비되지 않음");
|
||||
}
|
||||
}, []);
|
||||
|
||||
@@ -654,10 +312,9 @@ export function SheetViewer({ className }: SheetViewerProps) {
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (!isContainerReady) {
|
||||
// console.log("🔄 useEffect: DOM 컨테이너 재체크...");
|
||||
const timer = setTimeout(() => {
|
||||
if (containerRef.current && !isContainerReady) {
|
||||
// console.log("✅ useEffect: DOM 컨테이너 지연 준비 완료");
|
||||
console.log("✅ useEffect: DOM 컨테이너 지연 준비 완료");
|
||||
setIsContainerReady(true);
|
||||
}
|
||||
}, 100);
|
||||
@@ -666,43 +323,37 @@ export function SheetViewer({ className }: SheetViewerProps) {
|
||||
}, [isContainerReady]);
|
||||
|
||||
/**
|
||||
* 컴포넌트 마운트 시 초기화 - 블로그 포스트 방식 적용
|
||||
* 컴포넌트 마운트 시 초기화
|
||||
*/
|
||||
useEffect(() => {
|
||||
// console.log("🔍 useEffect 실행 조건 체크:", {
|
||||
// hasCurrentFile: !!currentFile,
|
||||
// hasXlsxBuffer: !!currentFile?.xlsxBuffer,
|
||||
// hasContainer: !!containerRef.current,
|
||||
// isContainerReady,
|
||||
// currentFileName: currentFile?.name,
|
||||
// bufferSize: currentFile?.xlsxBuffer?.byteLength,
|
||||
// });
|
||||
if (
|
||||
currentFile?.xlsxBuffer &&
|
||||
isContainerReady &&
|
||||
containerRef.current &&
|
||||
!isInitialized &&
|
||||
!isConverting
|
||||
) {
|
||||
console.log("🔄 XLSX 버퍼 감지, LuckyExcel 직접 변환 시작...", {
|
||||
fileName: currentFile.name,
|
||||
bufferSize: currentFile.xlsxBuffer.byteLength,
|
||||
containerId: containerRef.current.id,
|
||||
});
|
||||
|
||||
if (currentFile?.xlsxBuffer && isContainerReady && containerRef.current) {
|
||||
// console.log("🔄 변환된 XLSX 감지, LuckyExcel → Luckysheet 시작...", {
|
||||
// fileName: currentFile.name,
|
||||
// bufferSize: currentFile.xlsxBuffer.byteLength,
|
||||
// containerId: containerRef.current.id,
|
||||
// });
|
||||
// 중복 실행 방지
|
||||
setIsConverting(true);
|
||||
|
||||
// 변환된 XLSX ArrayBuffer를 사용하여 직접 변환 (블로그 포스트 방식)
|
||||
convertXLSXToLuckysheet(currentFile.xlsxBuffer, currentFile.name);
|
||||
// LuckyExcel로 직접 변환
|
||||
convertXLSXWithLuckyExcel(currentFile.xlsxBuffer, currentFile.name);
|
||||
} else if (currentFile && !currentFile.xlsxBuffer) {
|
||||
// console.warn(
|
||||
// "⚠️ currentFile은 있지만 xlsxBuffer가 없습니다:",
|
||||
// currentFile,
|
||||
// );
|
||||
setError("파일 변환 데이터가 없습니다. 파일을 다시 업로드해주세요.");
|
||||
} else if (!currentFile) {
|
||||
// console.log("ℹ️ currentFile이 없습니다. 파일을 업로드해주세요.");
|
||||
} else if (currentFile?.xlsxBuffer && !isContainerReady) {
|
||||
// console.log("⏳ DOM 컨테이너 준비 대기 중...");
|
||||
}
|
||||
}, [
|
||||
currentFile?.xlsxBuffer,
|
||||
currentFile?.name,
|
||||
isContainerReady,
|
||||
convertXLSXToLuckysheet,
|
||||
isInitialized,
|
||||
isConverting,
|
||||
convertXLSXWithLuckyExcel,
|
||||
]);
|
||||
|
||||
/**
|
||||
@@ -711,11 +362,10 @@ export function SheetViewer({ className }: SheetViewerProps) {
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (luckysheetRef.current && window.luckysheet) {
|
||||
// console.log("🧹 컴포넌트 언마운트: Luckysheet 정리 중...");
|
||||
try {
|
||||
window.luckysheet.destroy();
|
||||
} catch (error) {
|
||||
// console.warn("⚠️ Luckysheet 정리 중 오류:", error);
|
||||
console.warn("⚠️ Luckysheet 정리 중 오류:", error);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -728,9 +378,11 @@ export function SheetViewer({ className }: SheetViewerProps) {
|
||||
const handleResize = () => {
|
||||
if (luckysheetRef.current && window.luckysheet) {
|
||||
try {
|
||||
window.luckysheet.resize();
|
||||
if (window.luckysheet.resize) {
|
||||
window.luckysheet.resize();
|
||||
}
|
||||
} catch (error) {
|
||||
// console.warn("⚠️ Luckysheet 리사이즈 중 오류:", error);
|
||||
console.warn("⚠️ Luckysheet 리사이즈 중 오류:", error);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -771,7 +423,7 @@ export function SheetViewer({ className }: SheetViewerProps) {
|
||||
setIsInitialized(false);
|
||||
setIsConverting(false);
|
||||
if (currentFile?.xlsxBuffer) {
|
||||
convertXLSXToLuckysheet(
|
||||
convertXLSXWithLuckyExcel(
|
||||
currentFile.xlsxBuffer,
|
||||
currentFile.name,
|
||||
);
|
||||
@@ -793,11 +445,11 @@ export function SheetViewer({ className }: SheetViewerProps) {
|
||||
<div className="text-center p-6">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div>
|
||||
<div className="text-blue-600 text-lg font-semibold mb-2">
|
||||
{isConverting ? "XLSX 변환 중..." : "시트 초기화 중..."}
|
||||
{isConverting ? "LuckyExcel 변환 중..." : "시트 초기화 중..."}
|
||||
</div>
|
||||
<div className="text-blue-500 text-sm">
|
||||
{isConverting
|
||||
? "변환된 XLSX를 Luckysheet로 처리하고 있습니다."
|
||||
? "원본 Excel 데이터를 완전한 스타일로 변환하고 있습니다."
|
||||
: "잠시만 기다려주세요."}
|
||||
</div>
|
||||
</div>
|
||||
@@ -831,6 +483,7 @@ export function SheetViewer({ className }: SheetViewerProps) {
|
||||
<div>변환 중: {isConverting ? "예" : "아니오"}</div>
|
||||
<div>초기화: {isInitialized ? "완료" : "대기"}</div>
|
||||
<div>컨테이너 준비: {isContainerReady ? "완료" : "대기"}</div>
|
||||
<div>방식: LuckyExcel 직접 변환</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
174
src/components/sheet/TestSheetViewer.tsx
Normal file
174
src/components/sheet/TestSheetViewer.tsx
Normal file
@@ -0,0 +1,174 @@
|
||||
import React, { useRef, useEffect, useState } from "react";
|
||||
import { Univer, UniverInstanceType, LocaleType } from "@univerjs/core";
|
||||
import { defaultTheme } from "@univerjs/design";
|
||||
import { UniverDocsPlugin } from "@univerjs/docs";
|
||||
import { UniverDocsUIPlugin } from "@univerjs/docs-ui";
|
||||
import { UniverFormulaEnginePlugin } from "@univerjs/engine-formula";
|
||||
import { UniverRenderEnginePlugin } from "@univerjs/engine-render";
|
||||
import { UniverSheetsPlugin } from "@univerjs/sheets";
|
||||
import { UniverSheetsFormulaPlugin } from "@univerjs/sheets-formula";
|
||||
import { UniverSheetsFormulaUIPlugin } from "@univerjs/sheets-formula-ui";
|
||||
import { UniverSheetsUIPlugin } from "@univerjs/sheets-ui";
|
||||
import { UniverSheetsNumfmtPlugin } from "@univerjs/sheets-numfmt";
|
||||
import { UniverSheetsNumfmtUIPlugin } from "@univerjs/sheets-numfmt-ui";
|
||||
import { UniverUIPlugin } from "@univerjs/ui";
|
||||
|
||||
// 언어팩 import
|
||||
import DesignEnUS from "@univerjs/design/locale/en-US";
|
||||
import UIEnUS from "@univerjs/ui/locale/en-US";
|
||||
import DocsUIEnUS from "@univerjs/docs-ui/locale/en-US";
|
||||
import SheetsEnUS from "@univerjs/sheets/locale/en-US";
|
||||
import SheetsUIEnUS from "@univerjs/sheets-ui/locale/en-US";
|
||||
import SheetsFormulaUIEnUS from "@univerjs/sheets-formula-ui/locale/en-US";
|
||||
import SheetsNumfmtUIEnUS from "@univerjs/sheets-numfmt-ui/locale/en-US";
|
||||
|
||||
// CSS 스타일 import
|
||||
import "@univerjs/design/lib/index.css";
|
||||
import "@univerjs/ui/lib/index.css";
|
||||
import "@univerjs/docs-ui/lib/index.css";
|
||||
import "@univerjs/sheets-ui/lib/index.css";
|
||||
import "@univerjs/sheets-formula-ui/lib/index.css";
|
||||
import "@univerjs/sheets-numfmt-ui/lib/index.css";
|
||||
|
||||
/**
|
||||
* Univer CE 최소 구현 - 공식 문서 기반
|
||||
* 파일 업로드 없이 기본 스프레드시트만 표시
|
||||
*/
|
||||
const TestSheetViewer: React.FC = () => {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const univerRef = useRef<Univer | null>(null);
|
||||
const initializingRef = useRef<boolean>(false);
|
||||
const [isInitialized, setIsInitialized] = useState(false);
|
||||
|
||||
// Univer 초기화 - 공식 문서 패턴 따라서
|
||||
useEffect(() => {
|
||||
if (
|
||||
!containerRef.current ||
|
||||
isInitialized ||
|
||||
univerRef.current ||
|
||||
initializingRef.current
|
||||
)
|
||||
return;
|
||||
|
||||
const initializeUniver = async () => {
|
||||
try {
|
||||
initializingRef.current = true;
|
||||
console.log("🚀 Univer CE 초기화 시작");
|
||||
|
||||
// 1. Univer 인스턴스 생성
|
||||
const univer = new Univer({
|
||||
theme: defaultTheme,
|
||||
locale: LocaleType.EN_US,
|
||||
locales: {
|
||||
[LocaleType.EN_US]: {
|
||||
...DesignEnUS,
|
||||
...UIEnUS,
|
||||
...DocsUIEnUS,
|
||||
...SheetsEnUS,
|
||||
...SheetsUIEnUS,
|
||||
...SheetsFormulaUIEnUS,
|
||||
...SheetsNumfmtUIEnUS,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// 2. 필수 플러그인 등록 (공식 문서 순서)
|
||||
univer.registerPlugin(UniverRenderEnginePlugin);
|
||||
univer.registerPlugin(UniverFormulaEnginePlugin);
|
||||
|
||||
univer.registerPlugin(UniverUIPlugin, {
|
||||
container: containerRef.current!,
|
||||
});
|
||||
|
||||
univer.registerPlugin(UniverDocsPlugin);
|
||||
univer.registerPlugin(UniverDocsUIPlugin);
|
||||
|
||||
univer.registerPlugin(UniverSheetsPlugin);
|
||||
univer.registerPlugin(UniverSheetsUIPlugin);
|
||||
univer.registerPlugin(UniverSheetsFormulaPlugin);
|
||||
univer.registerPlugin(UniverSheetsFormulaUIPlugin);
|
||||
univer.registerPlugin(UniverSheetsNumfmtPlugin);
|
||||
univer.registerPlugin(UniverSheetsNumfmtUIPlugin);
|
||||
|
||||
// 3. 기본 워크북 생성
|
||||
univer.createUnit(UniverInstanceType.UNIVER_SHEET, {
|
||||
id: "test-workbook",
|
||||
name: "Test Workbook",
|
||||
sheetOrder: ["sheet1"],
|
||||
sheets: {
|
||||
sheet1: {
|
||||
id: "sheet1",
|
||||
name: "Sheet1",
|
||||
cellData: {
|
||||
0: {
|
||||
0: { v: "Hello Univer CE!" },
|
||||
1: { v: "환영합니다!" },
|
||||
},
|
||||
1: {
|
||||
0: { v: "Status" },
|
||||
1: { v: "Ready" },
|
||||
},
|
||||
},
|
||||
rowCount: 100,
|
||||
columnCount: 26,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
univerRef.current = univer;
|
||||
setIsInitialized(true);
|
||||
|
||||
console.log("✅ Univer CE 초기화 완료");
|
||||
} catch (error) {
|
||||
console.error("❌ Univer CE 초기화 실패:", error);
|
||||
initializingRef.current = false;
|
||||
}
|
||||
};
|
||||
|
||||
initializeUniver();
|
||||
}, [isInitialized]);
|
||||
|
||||
// 컴포넌트 언마운트 시 정리
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
try {
|
||||
if (univerRef.current) {
|
||||
univerRef.current.dispose();
|
||||
univerRef.current = null;
|
||||
}
|
||||
initializingRef.current = false;
|
||||
} catch (error) {
|
||||
console.error("❌ Univer dispose 오류:", error);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="w-full h-screen flex flex-col">
|
||||
{/* 간단한 헤더 */}
|
||||
<div className="bg-white border-b p-4 flex-shrink-0">
|
||||
<h1 className="text-xl font-bold">🧪 Univer CE 최소 테스트</h1>
|
||||
<div className="mt-2">
|
||||
<span
|
||||
className={`inline-flex items-center px-2 py-1 rounded-full text-xs font-medium ${
|
||||
isInitialized
|
||||
? "bg-green-100 text-green-800"
|
||||
: "bg-yellow-100 text-yellow-800"
|
||||
}`}
|
||||
>
|
||||
{isInitialized ? "✅ 초기화 완료" : "⏳ 초기화 중..."}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Univer 컨테이너 */}
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="flex-1"
|
||||
style={{ minHeight: "500px" }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TestSheetViewer;
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from "react";
|
||||
// import React from "react";
|
||||
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import "@testing-library/jest-dom";
|
||||
@@ -29,7 +29,7 @@ class MockDragEvent extends Event {
|
||||
// @ts-ignore
|
||||
global.DragEvent = MockDragEvent;
|
||||
|
||||
const mockUseAppStore = useAppStore as vi.MockedFunction<typeof useAppStore>;
|
||||
const mockUseAppStore = useAppStore as any;
|
||||
|
||||
describe("FileUpload", () => {
|
||||
const mockSetLoading = vi.fn();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from "react";
|
||||
// import React from "react";
|
||||
import { render, screen, waitFor } from "@testing-library/react";
|
||||
import "@testing-library/jest-dom";
|
||||
import { vi } from "vitest";
|
||||
|
||||
Reference in New Issue
Block a user