AI커맨드 반영 셀 선택 완료

This commit is contained in:
sheetEasy AI Team
2025-06-25 19:51:38 +09:00
parent 17d17511f5
commit 71036d3727
6 changed files with 3005 additions and 2297 deletions

View File

@@ -1,44 +1,24 @@
import React, { useRef, useEffect, useState, useCallback } 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";
// 공식 문서 권장: presets 패키지 사용으로 간소화
import {
createUniver,
defaultTheme,
LocaleType,
merge,
} from "@univerjs/presets";
import { UniverSheetsCorePreset } from "@univerjs/presets/preset-sheets-core";
import UniverPresetSheetsCoreEnUS from "@univerjs/presets/preset-sheets-core/locales/en-US";
import { UniverInstanceType } from "@univerjs/core";
// Presets CSS import
import "@univerjs/presets/lib/styles/preset-sheets-core.css";
import { cn } from "../../lib/utils";
import LuckyExcel from "@zwight/luckyexcel";
import PromptInput from "./PromptInput";
import { useAppStore } from "../../stores/useAppStore";
import { rangeToAddress } from "../../utils/cellUtils";
import { CellSelectionHandler } from "../../utils/cellSelectionHandler";
// Facade API imports - 공식 문서 방식 (필요한 기능만 선택적 import)
import "@univerjs/sheets/facade";
import "@univerjs/sheets-ui/facade";
// 언어팩 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";
import { aiProcessor } from "../../utils/aiProcessor";
// 전역 고유 키 생성
const GLOBAL_UNIVER_KEY = "__GLOBAL_UNIVER_INSTANCE__";
@@ -46,20 +26,21 @@ const GLOBAL_STATE_KEY = "__GLOBAL_UNIVER_STATE__";
// 전역 상태 인터페이스
interface GlobalUniverState {
instance: Univer | null;
instance: any | null; // createUniver 반환 타입
univerAPI: any | null; // univerAPI 별도 저장
isInitializing: boolean;
isDisposing: boolean;
initializationPromise: Promise<Univer> | null;
initializationPromise: Promise<any> | null;
lastContainerId: string | null;
}
// Window 객체에 전역 상태 확장
declare global {
interface Window {
[GLOBAL_UNIVER_KEY]: Univer | null;
[GLOBAL_UNIVER_KEY]: any | null;
[GLOBAL_STATE_KEY]: GlobalUniverState;
__UNIVER_DEBUG__: {
getGlobalUniver: () => Univer | null;
getGlobalUniver: () => any | null;
getGlobalState: () => GlobalUniverState;
clearGlobalState: () => void;
forceReset: () => void;
@@ -72,6 +53,7 @@ const initializeGlobalState = (): GlobalUniverState => {
if (!window[GLOBAL_STATE_KEY]) {
window[GLOBAL_STATE_KEY] = {
instance: null,
univerAPI: null,
isInitializing: false,
isDisposing: false,
initializationPromise: null,
@@ -87,21 +69,25 @@ const getGlobalState = (): GlobalUniverState => {
};
/**
* Window 객체 기반 강화된 전역 Univer 관리자
* 모듈 재로드와 HMR에도 안전하게 작동
* Presets 기반 강화된 전역 Univer 관리자
* 공식 문서 권장사항에 따라 createUniver 사용
*/
const UniverseManager = {
// 전역 인스턴스 생성 (완전 단일 인스턴스 보장)
async createInstance(container: HTMLElement): Promise<Univer> {
async createInstance(container: HTMLElement): Promise<any> {
const state = getGlobalState();
const containerId = container.id || `container-${Date.now()}`;
console.log(`🚀 Univer 인스턴스 생성 요청 - Container: ${containerId}`);
// 이미 존재하는 인스턴스가 있고 같은 컨테이너면 재사용
if (state.instance && state.lastContainerId === containerId) {
console.log("✅ 기존 전역 Univer 인스턴스 재사용");
return state.instance;
if (
state.instance &&
state.univerAPI &&
state.lastContainerId === containerId
) {
console.log("✅ 기존 전역 Univer 인스턴스와 univerAPI 재사용");
return { univer: state.instance, univerAPI: state.univerAPI };
}
// 초기화가 진행 중이면 대기
@@ -134,43 +120,30 @@ const UniverseManager = {
container.id = containerId;
}
console.log("🛠️ Univer 인스턴스 생성 중...");
const univer = new Univer({
theme: defaultTheme,
console.log("🛠️ Presets 기반 Univer 인스턴스 생성 중...");
// 공식 문서 권장: createUniver 사용으로 대폭 간소화
const { univer, univerAPI } = createUniver({
locale: LocaleType.EN_US,
locales: {
[LocaleType.EN_US]: {
...DesignEnUS,
...UIEnUS,
...DocsUIEnUS,
...SheetsEnUS,
...SheetsUIEnUS,
...SheetsFormulaUIEnUS,
...SheetsNumfmtUIEnUS,
},
[LocaleType.EN_US]: merge({}, UniverPresetSheetsCoreEnUS),
},
theme: defaultTheme,
presets: [
UniverSheetsCorePreset({
container: container,
}),
],
});
// 플러그인 등록 순서 (중요: Core → UI → Sheets → Docs → Formula → NumFmt)
console.log("🔌 플러그인 등록 중...");
univer.registerPlugin(UniverRenderEnginePlugin);
univer.registerPlugin(UniverUIPlugin, { container });
univer.registerPlugin(UniverSheetsPlugin);
univer.registerPlugin(UniverSheetsUIPlugin);
univer.registerPlugin(UniverDocsPlugin);
univer.registerPlugin(UniverDocsUIPlugin);
univer.registerPlugin(UniverSheetsFormulaPlugin);
univer.registerPlugin(UniverSheetsFormulaUIPlugin);
univer.registerPlugin(UniverSheetsNumfmtPlugin);
univer.registerPlugin(UniverSheetsNumfmtUIPlugin);
// 전역 상태 업데이트
// 전역 상태 업데이트 (univerAPI도 함께 저장)
state.instance = univer;
state.univerAPI = univerAPI;
state.lastContainerId = containerId;
window[GLOBAL_UNIVER_KEY] = univer;
console.log("✅ Univer 인스턴스 생성 완료");
return univer;
console.log("✅ Presets 기반 Univer 인스턴스와 univerAPI 생성 완료");
return { univer, univerAPI };
} catch (error) {
console.error("❌ Univer 인스턴스 생성 실패:", error);
throw error;
@@ -203,6 +176,7 @@ const UniverseManager = {
console.log("🗑️ 전역 Univer 인스턴스 dispose 시작");
await state.instance.dispose();
state.instance = null;
state.univerAPI = null;
state.lastContainerId = null;
window[GLOBAL_UNIVER_KEY] = null;
console.log("✅ 전역 Univer 인스턴스 dispose 완료");
@@ -214,7 +188,7 @@ const UniverseManager = {
},
// 현재 인스턴스 반환
getInstance(): Univer | null {
getInstance(): any | null {
const state = getGlobalState();
return state.instance || window[GLOBAL_UNIVER_KEY] || null;
},
@@ -240,6 +214,7 @@ const UniverseManager = {
}
state.instance = null;
state.univerAPI = null;
state.isInitializing = false;
state.isDisposing = false;
state.initializationPromise = null;
@@ -263,6 +238,7 @@ if (typeof window !== "undefined") {
const state = getGlobalState();
Object.assign(state, {
instance: null,
univerAPI: null,
isInitializing: false,
isDisposing: false,
initializationPromise: null,
@@ -298,7 +274,7 @@ const TestSheetViewer: React.FC = () => {
// CellSelectionHandler 인스턴스 생성
const cellSelectionHandler = useRef(new CellSelectionHandler());
// Univer 초기화 함수
// Univer 초기화 함수 (Presets 기반)
const initializeUniver = useCallback(
async (workbookData?: any) => {
if (!containerRef.current || !mountedRef.current) {
@@ -307,10 +283,10 @@ const TestSheetViewer: React.FC = () => {
}
try {
console.log("🚀 Univer 초기화 시작");
console.log("🚀 Presets 기반 Univer 초기화 시작");
// 전역 인스턴스 생성 또는 재사용
const univer = await UniverseManager.createInstance(
// 전역 인스턴스 생성 또는 재사용 (Presets 사용)
const { univer, univerAPI } = await UniverseManager.createInstance(
containerRef.current,
);
@@ -348,32 +324,29 @@ const TestSheetViewer: React.FC = () => {
const workbookToUse = workbookData || defaultWorkbook;
// 기존 워크북 정리 (API 호환성 고려)
// Presets에서는 univerAPI가 자동으로 제공됨
try {
const existingUnits =
(univer as any).getUnitsForType?.(
UniverInstanceType.UNIVER_SHEET,
) || [];
for (const unit of existingUnits) {
(univer as any).disposeUnit?.(unit.getUnitId());
if (univerAPI) {
console.log("✅ Presets 기반 univerAPI 초기화 완료");
// 새 워크북 생성 (presets univerAPI 방식)
const workbook = univerAPI.createWorkbook(workbookToUse);
console.log("✅ Presets 기반 워크북 생성 완료:", workbook?.getId());
// 셀 선택 핸들러 초기화 - SRP에 맞춰 별도 클래스로 분리
cellSelectionHandler.current.initialize(univer);
setIsInitialized(true);
} else {
console.warn("⚠️ univerAPI가 제공되지 않음");
setIsInitialized(false);
}
} catch (error) {
console.log(" 워크북 정리 시 오류 (무시 가능):", error);
console.error("❌ Presets 워크북 생성 오류:", error);
setIsInitialized(false);
}
// 새 워크북 생성
const workbook = univer.createUnit(
UniverInstanceType.UNIVER_SHEET,
workbookToUse,
);
console.log("✅ 워크북 생성 완료:", workbook?.getUnitId());
setIsInitialized(true);
// 셀 선택 핸들러 초기화 - SRP에 맞춰 별도 클래스로 분리
cellSelectionHandler.current.initialize(univer);
} catch (error) {
console.error("❌ Univer 초기화 실패:", error);
console.error("❌ Presets 기반 Univer 초기화 실패:", error);
setIsInitialized(false);
}
},
@@ -634,12 +607,7 @@ const TestSheetViewer: React.FC = () => {
<div style={{ height: "1rem" }} />
{/* 프롬프트 입력창 - Univer 하단에 이어서 */}
<PromptInput
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
onExecute={() => {}}
disabled={true}
/>
<PromptInput value={prompt} onChange={(e) => setPrompt(e.target.value)} />
{/* 파일 업로드 오버레이 - 레이어 분리 */}
{showUploadOverlay && (