@vrism/viewer-sdk
Version:
React and Vanilla JavaScript SDK for embedding 3D product viewers powered by Verge3D technology
446 lines (442 loc) • 12.3 kB
JavaScript
var h = Object.defineProperty;
var c = (n, e, r) => e in n ? h(n, e, { enumerable: !0, configurable: !0, writable: !0, value: r }) : n[e] = r;
var t = (n, e, r) => c(n, typeof e != "symbol" ? e + "" : e, r);
import * as l from "react";
import * as d from "react-dom/client";
import { VrismViewerReact as g } from "../react/index.js";
class f {
constructor(e) {
t(this, "currentConfig");
t(this, "baseProps");
t(this, "_apiBaseUrl");
this.currentConfig = e.config, this.baseProps = {
token: e.token,
contentId: e.contentId || "",
containerId: e.containerId
}, this._apiBaseUrl = e._apiBaseUrl;
}
updateConfig(e, r) {
typeof e == "string" ? this.currentConfig = {
...this.currentConfig,
[e]: r
} : this.currentConfig = {
...this.currentConfig,
...e
};
}
/**
* 콘텐츠 ID 변경
*/
updateContentId(e) {
this.baseProps.contentId = e;
}
/**
* 현재 설정 반환
*/
getConfig() {
return this.currentConfig;
}
/**
* React Props 형태로 변환
*/
toReactProps(e = {}) {
var i, a;
const r = {
...this.baseProps,
camera: (i = this.currentConfig) == null ? void 0 : i.camera,
ui: (a = this.currentConfig) == null ? void 0 : a.ui,
...e
};
return this._apiBaseUrl && (r._apiBaseUrl = this._apiBaseUrl), r;
}
}
class u {
constructor() {
// 사용자가 설정할 수 있는 이벤트 핸들러들
t(this, "onLoadFinish");
t(this, "onStepChange");
t(this, "onChange");
t(this, "onLoadScene");
t(this, "onLoadUpdate");
t(this, "onFullscreenChange");
t(this, "onError");
}
/**
* React Props 형태로 이벤트 핸들러 반환
* 바인딩된 this 컨텍스트를 유지
*/
getEventHandlers() {
const e = {};
return this.onChange && (e.onChange = this.onChange), this.onLoadScene && (e.onLoadScene = this.onLoadScene), this.onLoadUpdate && (e.onLoadUpdate = this.onLoadUpdate), this.onLoadFinish && (e.onLoadFinish = this.onLoadFinish), this.onStepChange && (e.onStepChange = this.onStepChange), this.onFullscreenChange && (e.onFullscreenChange = this.onFullscreenChange), this.onError && (e.onError = this.onError), e;
}
/**
* 모든 이벤트 핸들러 초기화
*/
clear() {
this.onLoadFinish = void 0, this.onStepChange = void 0, this.onChange = void 0, this.onLoadScene = void 0, this.onLoadUpdate = void 0, this.onFullscreenChange = void 0, this.onError = void 0;
}
}
class s {
constructor(e) {
t(this, "container");
t(this, "errorElement", null);
this.container = e.container, this.createErrorUI(e.error);
}
/**
* 에러 UI 생성
*/
createErrorUI(e) {
this.container.innerHTML = "";
const r = e instanceof Error ? e.message : String(e);
this.errorElement = document.createElement("div"), this.errorElement.className = "vrism-error-display", this.errorElement.innerHTML = `
<div class="vrism-error-container">
<h2 class="vrism-error-title">Error</h2>
<p class="vrism-error-message">${this.escapeHtml(r)}</p>
</div>
`, this.addStyles(), this.container.appendChild(this.errorElement);
}
/**
* HTML 이스케이프 처리
*/
escapeHtml(e) {
const r = document.createElement("div");
return r.textContent = e, r.innerHTML;
}
/**
* CSS 스타일 추가
*/
addStyles() {
if (document.getElementById("vrism-error-display-styles"))
return;
const e = document.createElement("style");
e.id = "vrism-error-display-styles", e.textContent = `
.vrism-error-display {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background-color: #ffffff;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.vrism-error-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 20px;
text-align: center;
max-width: 400px;
}
.vrism-error-title {
margin: 0;
font-size: 36px;
line-height: 42px;
color: #333333;
}
.vrism-error-message {
margin: 0;
font-size: 12px;
font-weight: 400;
color: #666666;
line-height: 1.5;
word-break: break-word;
}
`, document.head.appendChild(e);
}
/**
* 에러 표시 제거
*/
destroy() {
this.errorElement && this.errorElement.parentNode && (this.errorElement.parentNode.removeChild(this.errorElement), this.errorElement = null);
}
/**
* 정적 메서드: 간편 사용
*/
static show(e, r) {
return new s({ container: e, error: r });
}
}
class w {
constructor(e) {
t(this, "container");
t(this, "reactRoot", null);
t(this, "viewerRef", null);
t(this, "isInitialized", !1);
t(this, "errorDisplay", null);
this.container = e;
}
/**
* React 컴포넌트 렌더링
* @param props React 컴포넌트에 전달할 props
*/
render(e) {
this.clearErrorDisplay(), this.reactRoot || (this.reactRoot = d.createRoot(this.container));
const r = e.onError, a = {
...{
...e,
onError: (o) => {
this.showErrorDisplay(o), r && r(o);
}
},
ref: (o) => {
this.viewerRef = o, o && !this.isInitialized && (this.isInitialized = !0);
}
};
this.reactRoot.render(l.createElement(g, a));
}
/**
* React 컴포넌트의 ref 반환
* 모든 React 뷰어 메서드에 접근 가능
*/
getViewerRef() {
return this.viewerRef;
}
/**
* 뷰어 초기화 상태 확인
*/
isReady() {
return this.isInitialized && this.viewerRef !== null;
}
/**
* 초기화 완료까지 대기하는 Promise
*/
waitForInitialization() {
return new Promise((e, r) => {
if (this.isReady() && this.viewerRef) {
e(this.viewerRef);
return;
}
const i = setTimeout(() => {
r(new Error("Viewer initialization timeout"));
}, 1e4), a = () => {
this.isReady() && this.viewerRef ? (clearTimeout(i), e(this.viewerRef)) : setTimeout(a, 100);
};
a();
});
}
/**
* 에러 UI 표시
* React 컴포넌트를 언마운트하고 Vanilla ErrorDisplay로 대체
*/
showErrorDisplay(e) {
this.reactRoot && (this.reactRoot.unmount(), this.reactRoot = null), this.errorDisplay = s.show(this.container, e), this.isInitialized = !1, this.viewerRef = null;
}
/**
* 에러 표시 제거
*/
clearErrorDisplay() {
this.errorDisplay && (this.errorDisplay.destroy(), this.errorDisplay = null);
}
/**
* React 컴포넌트 언마운트 및 정리
*/
destroy() {
this.clearErrorDisplay(), this.reactRoot && (this.reactRoot.unmount(), this.reactRoot = null), this.viewerRef = null, this.isInitialized = !1;
}
}
class v {
constructor(e, r) {
t(this, "configManager");
t(this, "eventManager");
t(this, "reactBridge");
this.configManager = new f(r), this.eventManager = new u(), this.reactBridge = new w(e), r.onLoadFinish && (this.eventManager.onLoadFinish = r.onLoadFinish), r.onStepChange && (this.eventManager.onStepChange = r.onStepChange), r.onChange && (this.eventManager.onChange = r.onChange), r.onLoadScene && (this.eventManager.onLoadScene = r.onLoadScene), r.onLoadUpdate && (this.eventManager.onLoadUpdate = r.onLoadUpdate), r.onFullscreenChange && (this.eventManager.onFullscreenChange = r.onFullscreenChange), r.onError && (this.eventManager.onError = r.onError);
try {
this.render();
} catch (i) {
this.handleError(i);
}
}
/**
* React 컴포넌트 렌더링
* 설정과 이벤트 핸들러를 React Props로 변환
*/
render() {
const e = this.eventManager.getEventHandlers(), r = this.configManager.toReactProps(e);
this.reactBridge.render(r);
}
/**
* React 뷰어 ref에 안전하게 접근
* 뷰어가 초기화되지 않은 경우 에러 발생
*/
async getViewerRef() {
return this.reactBridge.isReady() ? this.reactBridge.getViewerRef() : await this.reactBridge.waitForInitialization();
}
/**
* 에러 처리 헬퍼 메서드
* 콘솔 로그와 사용자 콜백 모두 호출
*/
handleError(e) {
console.error("VRISM Viewer Error:", e), this.eventManager.onError && this.eventManager.onError(e);
}
// ==================== 공개 API 메서드들 ====================
/**
* 새로운 콘텐츠 로드
*/
load(e) {
this.configManager.updateContentId(e.contentId), this.render();
}
setConfig(e, r) {
this.configManager.updateConfig(e, r), this.render();
}
/**
* 현재 설정 조회
*/
getConfig() {
return this.configManager.getConfig();
}
/**
* 뷰어 리로드
*/
reload(e) {
e && this.configManager.updateConfig(e), this.render();
}
/**
* 비동기 뷰어 리로드 (React 구현체와 동일한 Promise 기반)
*/
async reloadAsync(e) {
try {
return (await this.getViewerRef()).reload(e);
} catch (r) {
throw this.handleError(r), r;
}
}
/**
* 뷰어 내부 설정 조회 (React 구현체에서 제공)
*/
async getViewerConfig() {
try {
return (await this.getViewerRef()).getConfig();
} catch (e) {
throw this.handleError(e), e;
}
}
/**
* 3D 위치 선택 API
*/
async getClickedPosition() {
try {
return (await this.getViewerRef()).getClickedPosition();
} catch (e) {
throw this.handleError(e), e;
}
}
/**
* 제스처 가이드 표시 제어
*/
async setGestureGuideShow(e) {
try {
return (await this.getViewerRef()).setGestureGuideShow(e);
} catch (r) {
throw this.handleError(r), r;
}
}
/**
* 전체화면 모드 제어
*/
async setFullscreenOpen(e) {
try {
return (await this.getViewerRef()).setFullscreenOpen(e);
} catch (r) {
throw this.handleError(r), r;
}
}
/**
* 스텝 설정
*/
async setStep(e) {
try {
return (await this.getViewerRef()).setStep(e);
} catch (r) {
throw this.handleError(r), r;
}
}
/**
* VTO 기능 트리거
*/
async onVTOClick() {
try {
return (await this.getViewerRef()).onVTOClick();
} catch (e) {
throw this.handleError(e), e;
}
}
// ==================== 이벤트 핸들러 접근자들 ====================
get onLoadFinish() {
return this.eventManager.onLoadFinish;
}
set onLoadFinish(e) {
this.eventManager.onLoadFinish = e;
}
get onStepChange() {
return this.eventManager.onStepChange;
}
set onStepChange(e) {
this.eventManager.onStepChange = e;
}
get onChange() {
return this.eventManager.onChange;
}
set onChange(e) {
this.eventManager.onChange = e;
}
get onLoadScene() {
return this.eventManager.onLoadScene;
}
set onLoadScene(e) {
this.eventManager.onLoadScene = e;
}
get onLoadUpdate() {
return this.eventManager.onLoadUpdate;
}
set onLoadUpdate(e) {
this.eventManager.onLoadUpdate = e;
}
get onFullscreenChange() {
return this.eventManager.onFullscreenChange;
}
set onFullscreenChange(e) {
this.eventManager.onFullscreenChange = e;
}
get onError() {
return this.eventManager.onError;
}
set onError(e) {
this.eventManager.onError = e;
}
// ==================== 정리 ====================
/**
* 뷰어 인스턴스 정리
* React 컴포넌트 언마운트 및 메모리 정리
*/
destroy() {
this.eventManager.clear(), this.reactBridge.destroy();
}
}
const C = {
/**
* 뷰어 인스턴스 초기화
* @param containerId DOM 컨테이너 엘리먼트 ID
* @param options 초기화 옵션
* @returns VrismViewerInstance 인스턴스
*/
init: (n, e) => {
const r = document.getElementById(n);
if (!r)
throw new Error(`Container element with id "${n}" not found`);
const i = {
...e,
containerId: n
};
return new v(r, i);
}
};
export {
f as ConfigManager,
s as ErrorDisplay,
u as EventManager,
w as ReactBridge,
C as VrismViewer,
v as VrismViewerInstance,
C as default
};