UNPKG

react-krpano-toolkit

Version:

A React toolkit for KRPano integration with reusable functions and screen/hotspot configuration management

1,301 lines (1,270 loc) 53 kB
'use strict'; var react = require('react'); var jsxRuntime = require('react/jsx-runtime'); /* eslint-disable @typescript-eslint/no-explicit-any */ var KrpanoAPI = /** @class */function () { function KrpanoAPI() { this.instance = null; } KrpanoAPI.prototype.setInstance = function (krpano) { this.instance = krpano; }; KrpanoAPI.prototype.isReady = function () { return !!this.instance; }; KrpanoAPI.prototype.call = function (action) { var _a, _b; if (!this.instance) { console.error("instance Krpano chưa sẵn sàng"); return {}; } return (_b = (_a = this.instance) === null || _a === void 0 ? void 0 : _a.call) === null || _b === void 0 ? void 0 : _b.call(_a, action); }; KrpanoAPI.prototype.get = function (path) { var _a, _b; if (!this.instance) throw new Error("Krpano chưa sẵn sàng"); return (_b = (_a = this.instance) === null || _a === void 0 ? void 0 : _a.get) === null || _b === void 0 ? void 0 : _b.call(_a, path); }; KrpanoAPI.prototype.set = function (path, value) { var _a, _b; if (!this.instance) throw new Error("Krpano chưa sẵn sàng"); (_b = (_a = this.instance) === null || _a === void 0 ? void 0 : _a.set) === null || _b === void 0 ? void 0 : _b.call(_a, path, value); }; return KrpanoAPI; }(); // singleton instance var _krpanoAPI = null; // export tên giống trước, nhưng là getter var krpanoAPI = new Proxy({}, { get: function get(_, prop) { if (!_krpanoAPI) _krpanoAPI = new KrpanoAPI(); // @ts-ignore return _krpanoAPI[prop]; }, set: function set(_, prop, value) { if (!_krpanoAPI) _krpanoAPI = new KrpanoAPI(); // @ts-ignore _krpanoAPI[prop] = value; return true; } }); var EventBus = /** @class */function () { function EventBus() { this.events = {}; } EventBus.prototype.on = function (event, listener) { if (!this.events[event]) this.events[event] = []; this.events[event].push(listener); }; EventBus.prototype.off = function (event, listener) { this.events[event] = (this.events[event] || []).filter(function (l) { return l !== listener; }); }; EventBus.prototype.emit = function (event) { var args = []; for (var _i = 1; _i < arguments.length; _i++) { args[_i - 1] = arguments[_i]; } (this.events[event] || []).forEach(function (listener) { return listener.apply(void 0, args); }); }; EventBus.prototype.clear = function () { this.events = {}; }; return EventBus; }(); var KrpanoContext = /*#__PURE__*/react.createContext({ api: new KrpanoAPI(), events: new EventBus() }); var KrpanoProvider = function KrpanoProvider(_a) { var children = _a.children; var apiRef = react.useRef(new KrpanoAPI()); var eventBusRef = react.useRef(new EventBus()); react.useEffect(function () { window.onKrpanoReady = function (instance) { apiRef.current.setInstance(instance); eventBusRef.current.emit("ready", instance); }; }, []); return jsxRuntime.jsx(KrpanoContext.Provider, { value: { api: apiRef.current, events: eventBusRef.current }, children: children }); }; var useExecute = function useExecute() { var ctx = react.useContext(KrpanoContext); if (!ctx) throw new Error("KrpanoContext chưa được cung cấp"); var api = ctx.api; var execute = function execute(opts) { var type = opts.type, name = opts.name, options = opts.options, raw = opts.raw; if (raw) { api.call(raw); return; } if (!type) { console.warn("type là bắt buộc trừ khi dùng raw"); return; } if (!name && !["action", "scene", "view", "events", "display", "control"].includes(type)) { console.warn("name là bắt buộc cho loại này"); return; } var basePath = name ? "".concat(type, "[").concat(name, "]") : type; if (!options) { console.warn("options chưa được cung cấp"); return; } Object.entries(options).forEach(function (_a) { var option = _a[0], value = _a[1]; api.set("".concat(basePath, ".").concat(option), value); }); }; return execute; }; var useScene = function useScene() { var ctx = react.useContext(KrpanoContext); if (!ctx) throw new Error("KrpanoContext chưa được cung cấp"); var api = ctx.api; // Scene operations var scene = { loadScene: function loadScene(sceneName, options) { if (options === void 0) { options = {}; } var _a = options.blend, blend = _a === void 0 ? "BLEND(0.5)" : _a, _b = options.flags, flags = _b === void 0 ? "" : _b; api.call("loadscene(".concat(sceneName, ", null, ").concat(blend, ", ").concat(flags, ")")); }, removeScene: function removeScene(sceneName) { api.call("removescene(".concat(sceneName, ")")); }, addScene: function addScene(sceneName, xmlPath) { if (xmlPath) { api.call("addscene(".concat(sceneName, "); loadxml(").concat(xmlPath, ")")); } else { api.call("addscene(".concat(sceneName, ")")); } }, copyScene: function copyScene(fromScene, toScene) { api.call("copyscene(".concat(fromScene, ", ").concat(toScene, ")")); } }; return scene; }; var useView = function useView() { var ctx = react.useContext(KrpanoContext); if (!ctx) throw new Error("KrpanoContext chưa được cung cấp"); var api = ctx.api; // View operations var view = { lookAt: function lookAt(hlookat, vlookat, fov, options) { if (options === void 0) { options = {}; } var _a = options.smooth, smooth = _a === void 0 ? true : _a, _b = options.time, time = _b === void 0 ? 1.0 : _b, _c = options.tween, tween = _c === void 0 ? "easeInOutSine" : _c, blend = options.blend; var blendParam = blend !== undefined ? ", ".concat(blend) : ''; if (smooth) { api.call("tween(view.hlookat|view.vlookat".concat(fov ? '|view.fov' : '', ", ").concat(hlookat, "|").concat(vlookat).concat(fov ? "|".concat(fov) : '', ", ").concat(time, ", ").concat(tween).concat(blendParam, ")")); } else { api.set('view.hlookat', hlookat); api.set('view.vlookat', vlookat); if (fov !== undefined) api.set('view.fov', fov); } }, lookTo: function lookTo(ath, atv, options) { if (options === void 0) { options = {}; } var _a = options.smooth, smooth = _a === void 0 ? true : _a, _b = options.time, time = _b === void 0 ? 1.0 : _b; if (smooth) { api.call("lookto(".concat(ath, ", ").concat(atv, ", ").concat(time, ")")); } else { api.call("looktohotspot('', ".concat(ath, ", ").concat(atv, ")")); } }, zoomTo: function zoomTo(fov, options) { if (options === void 0) { options = {}; } var _a = options.smooth, smooth = _a === void 0 ? true : _a, _b = options.time, time = _b === void 0 ? 1.0 : _b; if (smooth) { api.call("tween(view.fov, ".concat(fov, ", ").concat(time, ")")); } else { api.set('view.fov', fov); } }, moveCamera: function moveCamera(direction, amount) { if (amount === void 0) { amount = 5; } var directions = { up: "add(view.vlookat, ".concat(amount, ")"), down: "add(view.vlookat, -".concat(amount, ")"), left: "add(view.hlookat, -".concat(amount, ")"), right: "add(view.hlookat, ".concat(amount, ")") }; api.call(directions[direction]); }, stopMovement: function stopMovement() { api.call('stopdelayedcall(); stoptween();'); } }; return view; }; var useElement = function useElement() { var ctx = react.useContext(KrpanoContext); if (!ctx) { throw new Error("KrpanoContext chưa được cung cấp"); } var api = ctx.api; // Element operations var element = { show: function show(type, name, options) { if (options === void 0) { options = {}; } var time = options.time, _a = options.tween, tween = _a === void 0 ? "easeInOutSine" : _a; if (time) { api.call("tween(".concat(type, "[").concat(name, "].alpha, 1.0, ").concat(time, ", ").concat(tween, "); set(").concat(type, "[").concat(name, "].visible, true);")); } else { api.set("".concat(type, "[").concat(name, "].visible"), true); api.set("".concat(type, "[").concat(name, "].alpha"), 1.0); } }, hide: function hide(type, name, options) { if (options === void 0) { options = {}; } var time = options.time, _a = options.tween, tween = _a === void 0 ? "easeInOutSine" : _a; if (time) { api.call("tween(".concat(type, "[").concat(name, "].alpha, 0.0, ").concat(time, ", ").concat(tween, ", set(").concat(type, "[").concat(name, "].visible, false));")); } else { api.set("".concat(type, "[").concat(name, "].visible"), false); } }, toggle: function toggle(type, name) { var visible = api.get("".concat(type, "[").concat(name, "].visible")); api.call("set(".concat(type, "[").concat(name, "].visible, ").concat(visible ? 'false' : 'true', ");")); }, animate: function animate(type, name, properties, options) { if (options === void 0) { options = {}; } var _a = options.time, time = _a === void 0 ? 1.0 : _a, _b = options.tween, tween = _b === void 0 ? "easeInOutSine" : _b, onComplete = options.onComplete; var propStrings = Object.entries(properties).map(function (_a) { var key = _a[0]; _a[1]; return "".concat(type, "[").concat(name, "].").concat(key); }).join('|'); var valueStrings = Object.values(properties).join('|'); var onCompleteParam = onComplete ? ", ".concat(onComplete) : ''; api.call("tween(".concat(propStrings, ", ").concat(valueStrings, ", ").concat(time, ", ").concat(tween).concat(onCompleteParam, ")")); }, addElement: function addElement(type, name, properties) { if (properties === void 0) { properties = {}; } var elementType = properties.type || type; var parent = properties.parent || "container"; api.call("add".concat(type, "(").concat(name, ")")); api.call("set(".concat(type, "[").concat(name, "].parent, ").concat(parent, ")")); api.call("set(".concat(type, "[").concat(name, "].type, ").concat(elementType, ")")); Object.entries(properties).forEach(function (_a) { var key = _a[0], value = _a[1]; if (["type", "parent", "onloaded"].includes(key)) return; var v = typeof value === "string" ? "\"".concat(value, "\"") : value; api.call("set(".concat(type, "[").concat(name, "].").concat(key, ", ").concat(v, ")")); }); }, updateElement: function updateElement(type, name, properties) { if (properties === void 0) { properties = {}; } if (!api.get("".concat(type, "[").concat(name, "]"))) { console.warn("".concat(type, "[").concat(name, "] kh\xF4ng t\u1ED3n t\u1EA1i, h\xE3y d\xF9ng addElement tr\u01B0\u1EDBc.")); return; } Object.entries(properties).forEach(function (_a) { var key = _a[0], value = _a[1]; if (["type", "parent", "onloaded"].includes(key)) return; var v = typeof value === "string" ? "\"".concat(value, "\"") : value; api.call("set(".concat(type, "[").concat(name, "].").concat(key, ", ").concat(v, ")")); }); }, removeElement: function removeElement(type, name) { api.call("remove".concat(type, "(").concat(name, ")")); }, cloneElement: function cloneElement(type, fromName, toName) { var _a; if (!api.get("".concat(type, "[").concat(fromName, "]"))) { console.warn("".concat(type, "[").concat(fromName, "] kh\xF4ng t\u1ED3n t\u1EA1i, kh\xF4ng th\u1EC3 clone.")); return false; } if (api.get("".concat(type, "[").concat(toName, "]"))) { console.warn("".concat(type, "[").concat(toName, "] \u0111\xE3 t\u1ED3n t\u1EA1i, kh\xF4ng th\u1EC3 clone.")); return false; } api.call("add".concat(type, "(").concat(toName, ")")); // Lấy tất cả thuộc tính của element gốc var props = (_a = api.get("".concat(type, "[").concat(fromName, "]"))) === null || _a === void 0 ? void 0 : _a.DATA; if (!props) { console.warn("Kh\xF4ng th\u1EC3 l\u1EA5y thu\u1ED9c t\xEDnh c\u1EE7a ".concat(type, "[").concat(fromName, "]")); return false; } // Gán tất cả thuộc tính cho element mới Object.entries(props).forEach(function (_a) { var key = _a[0], value = _a[1]; if (key === "name") return; // bỏ qua thuộc tính name var v = typeof value === "string" ? "\"".concat(value, "\"") : value; api.call("set(".concat(type, "[").concat(toName, "].").concat(key, ", ").concat(v, ")")); }); return true; } }; return element; }; var useSound = function useSound() { var ctx = react.useContext(KrpanoContext); if (!ctx) throw new Error("KrpanoContext chưa được cung cấp"); var api = ctx.api; // Sound operations var sound = { playSound: function playSound(soundName, url, options) { if (options === void 0) { options = {}; } var _a = options.volume, volume = _a === void 0 ? 1.0 : _a, _b = options.loops, loops = _b === void 0 ? 1 : _b; if (url) { api.call("loadsound(".concat(soundName, ", ").concat(url, "); playsound(").concat(soundName, ", ").concat(volume, ", ").concat(loops, ")")); } else { api.call("playsound(".concat(soundName, ", ").concat(volume, ", ").concat(loops, ")")); } }, stopSound: function stopSound(soundName) { api.call("stopsound(".concat(soundName, ")")); }, pauseSound: function pauseSound(soundName) { api.call("pausesound(".concat(soundName, ")")); }, setSoundVolume: function setSoundVolume(soundName, volume) { api.set("sound[".concat(soundName, "].volume"), volume); }, stopAllSounds: function stopAllSounds() { api.call('stopallsounds()'); } }; return sound; }; var useControl = function useControl() { var ctx = react.useContext(KrpanoContext); if (!ctx) { console.error("KrpanoContext chưa được cung cấp"); return null; } var api = ctx.api; // Control operations var control = { enterFullscreen: function enterFullscreen() { return api.set('fullscreen', true); }, exitFullscreen: function exitFullscreen() { return api.set('fullscreen', false); }, toggleFullscreen: function toggleFullscreen() { return api.set('fullscreen', !api.get('fullscreen')); }, enableControl: function enableControl() { return api.set('control.usercontrol', true); }, disableControl: function disableControl() { return api.set('control.usercontrol', false); }, toggleAutoRotate: function toggleAutoRotate() { if (api.get("autorotate.enabled") === true) { api.set("autorotate.enabled", false); } else { api.set("autorotate.enabled", true); } }, getAutoRotateStatus: function getAutoRotateStatus() { return api.get("autorotate.enabled") === true; } }; return control; }; var useUtility = function useUtility() { var ctx = react.useContext(KrpanoContext); if (!ctx) throw new Error("KrpanoContext chưa được cung cấp"); var api = ctx.api; // Utility operations var utility = { loadXML: function loadXML(url, options) { if (options === void 0) { options = {}; } var _a = options.keepdisplay, keepdisplay = _a === void 0 ? false : _a; api.call("loadxml(".concat(url, ", ").concat(keepdisplay, ")")); }, reload: function reload() { return api.call('reload()'); }, screenshot: function screenshot(options) { if (options === void 0) { options = {}; } var _a = options.scale, scale = _a === void 0 ? 1.0 : _a, _b = options.alpha, alpha = _b === void 0 ? false : _b; api.call("screenshot(".concat(scale, ", ").concat(alpha, ")")); }, trace: function trace(message) { return api.call("trace(".concat(message, ")")); }, error: function error(message) { return api.call("error(".concat(message, ")")); }, get: function get(variable) { return api.get(variable); }, set: function set(variable, value) { return api.set(variable, value); }, call: function call(action) { return api.call(action); }, callwith: function callwith(action) { var args = []; for (var _i = 1; _i < arguments.length; _i++) { args[_i - 1] = arguments[_i]; } return api.call("callwith(".concat(action, ", ").concat(args.join(', '), ")")); }, wait: function wait(time, callback) { var callbackParam = callback ? ", ".concat(callback) : ''; api.call("delayedcall(".concat(time).concat(callbackParam, ")")); } }; return utility; }; /** * Hook to access the Krpano API context. * @returns {call, get, set} - The Krpano API context. * @throws {Error} - If useKrpano is called outside of KrpanoProvider. */ function useKrpano() { var ctx = react.useContext(KrpanoContext); if (!ctx) throw new Error("KrpanoContext chưa được cung cấp"); // Import other hooks var execute = useExecute(); var scene = useScene(); var view = useView(); var element = useElement(); var sound = useSound(); var control = useControl(); var utility = useUtility(); return { execute: execute, scene: scene, view: view, element: element, sound: sound, control: control, utility: utility, // Shortcuts for common operations show: function show(name, type) { if (type === void 0) { type = 'hotspot'; } return element.show(type, name); }, hide: function hide(name, type) { if (type === void 0) { type = 'hotspot'; } return element.hide(type, name); }, toggle: function toggle(name, type) { if (type === void 0) { type = 'hotspot'; } return element.toggle(type, name); }, loadScene: scene.loadScene, lookAt: view.lookAt, zoomTo: view.zoomTo }; } function useKrpanoGlobalAction() { var addAction = function addAction(name, callback) { if (!krpanoAPI) return; krpanoAPI.set("action[".concat(name, "]"), callback({})); }; var callAction = function callAction(name, params) { if (!krpanoAPI) return; if (params) { var args = Object.values(params).join(", "); krpanoAPI.call("".concat(name, "(").concat(args, ")")); } else { krpanoAPI.call("".concat(name, "()")); } }; return { addAction: addAction, callAction: callAction }; } function useLoading() { var _a = react.useState(0), progress = _a[0], setProgress = _a[1]; var _b = react.useContext(KrpanoContext), api = _b.api, events = _b.events; react.useEffect(function () { if (!api || !events) return; var interval; var startProgress = function startProgress() { interval = setInterval(function () { setProgress(function (prev) { var next = prev + Math.random() * 5; return Math.min(Math.floor(next), 95); }); }, 100); }; var finishProgress = function finishProgress() { clearInterval(interval); setProgress(100); }; if (api.isReady()) { startProgress(); finishProgress(); } else { startProgress(); events.on("ready", finishProgress); } return function () { clearInterval(interval); events.off("ready", finishProgress); }; }, [api, events]); var isLoaded = progress >= 100; return { progress: progress, isLoaded: isLoaded }; } /****************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */ var __assign = function() { __assign = Object.assign || function __assign(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { var e = new Error(message); return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; }; function useKrpanoEventBridge() { var ctx = react.useContext(KrpanoContext); if (!ctx) throw new Error("KrpanoContext chưa được cung cấp"); var api = ctx.api; var handlersRef = react.useRef(new Map()); // Khởi tạo global event bridge react.useEffect(function () { if (typeof window !== 'undefined') { // Tạo global bridge function để Krpano có thể gọi window.krpanoEventBridge = function (eventType, eventData) { var handlers = handlersRef.current.get(eventType); if (handlers) { var processedData_1 = __assign({ type: eventType, timestamp: Date.now() }, eventData); handlers.forEach(function (handler) { try { handler(processedData_1); } catch (error) { console.error("Error in Krpano event handler for ".concat(eventType, ":"), error); } }); } }; // Setup Krpano event listeners setupKrpanoEventListeners(); } return function () { if (typeof window !== 'undefined') { delete window.krpanoEventBridge; } }; }, [api]); // Setup các event listeners trong Krpano var setupKrpanoEventListeners = react.useCallback(function () { if (!api) return; // System Events api.set('events.onloadcomplete', 'js(krpanoEventBridge("onloadcomplete", {}))'); api.set('events.onxmlcomplete', 'js(krpanoEventBridge("onxmlcomplete", {}))'); api.set('events.onready', 'js(krpanoEventBridge("onready", {}))'); api.set('events.onerror', 'js(krpanoEventBridge("onerror", {error: get(lasterror)}))'); api.set('events.onresize', 'js(krpanoEventBridge("onresize", {width: get(stagewidth), height: get(stageheight)}))'); api.set('events.onfullscreenchange', 'js(krpanoEventBridge("onfullscreenchange", {fullscreen: get(fullscreen)}))'); // Scene Events api.set('events.onnewscene', 'js(krpanoEventBridge("onnewscene", {scenename: get(xml.scene), sceneindex: get(xml.sceneindex)}))'); api.set('events.onremovescene', 'js(krpanoEventBridge("onremovescene", {scenename: get(xml.scene)}))'); // View Events api.set('events.onviewchange', 'js(krpanoEventBridge("onviewchange", {hlookat: get(view.hlookat), vlookat: get(view.vlookat), fov: get(view.fov)}))'); api.set('events.onviewchanged', 'js(krpanoEventBridge("onviewchanged", {hlookat: get(view.hlookat), vlookat: get(view.vlookat), fov: get(view.fov)}))'); api.set('events.onidle', 'js(krpanoEventBridge("onidle", {}))'); // Mouse Events api.set('events.onclick', 'js(krpanoEventBridge("onclick", {stagex: get(mouse.stagex), stagey: get(mouse.stagey)}))'); api.set('events.ondblclick', 'js(krpanoEventBridge("ondblclick", {stagex: get(mouse.stagex), stagey: get(mouse.stagey)}))'); api.set('events.onmousedown', 'js(krpanoEventBridge("onmousedown", {stagex: get(mouse.stagex), stagey: get(mouse.stagey), button: get(mouse.button)}))'); api.set('events.onmouseup', 'js(krpanoEventBridge("onmouseup", {stagex: get(mouse.stagex), stagey: get(mouse.stagey), button: get(mouse.button)}))'); api.set('events.onmousemove', 'js(krpanoEventBridge("onmousemove", {stagex: get(mouse.stagex), stagey: get(mouse.stagey)}))'); // Keyboard Events api.set('events.onkeydown', 'js(krpanoEventBridge("onkeydown", {keycode: get(key.keycode), key: get(key.key), ctrlkey: get(key.ctrlkey), shiftkey: get(key.shiftkey), altkey: get(key.altkey)}))'); api.set('events.onkeyup', 'js(krpanoEventBridge("onkeyup", {keycode: get(key.keycode), key: get(key.key), ctrlkey: get(key.ctrlkey), shiftkey: get(key.shiftkey), altkey: get(key.altkey)}))'); }, [api]); // Thêm event listener var addEventListener = react.useCallback(function (eventType, handler) { var _a; if (!handlersRef.current.has(eventType)) { handlersRef.current.set(eventType, new Set()); } (_a = handlersRef.current.get(eventType)) === null || _a === void 0 ? void 0 : _a.add(handler); // Return cleanup function return function () { var _a; (_a = handlersRef.current.get(eventType)) === null || _a === void 0 ? void 0 : _a["delete"](handler); }; }, []); // Xóa event listener var removeEventListener = react.useCallback(function (eventType, handler) { var _a; (_a = handlersRef.current.get(eventType)) === null || _a === void 0 ? void 0 : _a["delete"](handler); }, []); // Xóa tất cả listeners của một event var removeAllEventListeners = react.useCallback(function (eventType) { if (eventType) { handlersRef.current["delete"](eventType); } else { handlersRef.current.clear(); } }, []); // Trigger custom event var triggerCustomEvent = react.useCallback(function (eventType, data) { if (typeof window !== 'undefined' && window.krpanoEventBridge) { window.krpanoEventBridge(eventType, data); } }, []); return { addEventListener: addEventListener, removeAllEventListeners: removeAllEventListeners, triggerCustomEvent: triggerCustomEvent, removeEventListener: removeEventListener }; } /* eslint-disable @typescript-eslint/no-explicit-any */ function useKrpanoCommand() { var ctx = react.useContext(KrpanoContext); if (!ctx) throw new Error("KrpanoContext chưa được cung cấp"); return ctx; } function useLayerEvents(layerName, handlers) { var ctx = react.useContext(KrpanoContext); if (!ctx) throw new Error("KrpanoContext chưa được cung cấp"); var api = ctx.api; var addEventListener = useKrpanoEventBridge().addEventListener; react.useEffect(function () { if (!api || !layerName) return; var cleanupFns = []; // onclick if (handlers.onclick) { api.set("layer[".concat(layerName, "].onclick"), "js(krpanoEventBridge(\"onlayerclick\", {layername:\"".concat(layerName, "\", stagex:get(mouse.stagex), stagey:get(mouse.stagey)}))")); var cleanup = addEventListener('onlayerclick', function (data) { if ('layername' in data && data.layername === layerName) { handlers.onclick(data); } }); cleanupFns.push(cleanup); } // onover if (handlers.onover) { api.set("layer[".concat(layerName, "].onover"), "js(krpanoEventBridge(\"onlayerover\", {layername:\"".concat(layerName, "\"}))")); var cleanup = addEventListener('onlayerover', function (data) { if ('layername' in data && data.layername === layerName) { handlers.onover(data); } }); cleanupFns.push(cleanup); } // onout if (handlers.onout) { api.set("layer[".concat(layerName, "].onout"), "js(krpanoEventBridge(\"onlayerout\", {layername:\"".concat(layerName, "\"}))")); var cleanup = addEventListener('onlayerout', function (data) { if ('layername' in data && data.layername === layerName) { handlers.onout(data); } }); cleanupFns.push(cleanup); } return function () { cleanupFns.forEach(function (fn) { return fn(); }); }; }, [api, layerName, handlers, addEventListener]); } var useHotspotEvent = function useHotspotEvent(hotspotName, handlers) { var ctx = react.useContext(KrpanoContext); if (!ctx) throw new Error("KrpanoContext chưa được cung cấp"); var api = ctx.api; var addEventListener = useKrpanoEventBridge().addEventListener; react.useEffect(function () { if (!api) return function () {}; var cleanupFunctions = []; if (handlers.onclick) { api.set("hotspot[".concat(hotspotName, "].onclick"), "js(krpanoEventBridge(\"onhotspotclick\", {hotspotname: \"".concat(hotspotName, "\", stagex: get(mouse.stagex), stagey: get(mouse.stagey), ath: get(hotspot[").concat(hotspotName, "].ath), atv: get(hotspot[").concat(hotspotName, "].atv)}))")); var cleanup = addEventListener('onhotspotclick', function (data) { if ('hotspotname' in data && data.hotspotname === hotspotName) { handlers.onclick(data); } }); cleanupFunctions.push(cleanup); } if (handlers.onover) { api.set("hotspot[".concat(hotspotName, "].onover"), "js(krpanoEventBridge(\"onhotspotover\", {hotspotname: \"".concat(hotspotName, "\", ath: get(hotspot[").concat(hotspotName, "].ath), atv: get(hotspot[").concat(hotspotName, "].atv)}))")); var cleanup = addEventListener('onhotspotover', function (data) { if ('hotspotname' in data && data.hotspotname === hotspotName) { handlers.onover(data); } }); cleanupFunctions.push(cleanup); } if (handlers.onout) { api.set("hotspot[".concat(hotspotName, "].onout"), "js(krpanoEventBridge(\"onhotspotout\", {hotspotname: \"".concat(hotspotName, "\"}))")); var cleanup = addEventListener('onhotspotout', function (data) { if ('hotspotname' in data && data.hotspotname === hotspotName) { handlers.onout(data); } }); cleanupFunctions.push(cleanup); } return function () { cleanupFunctions.forEach(function (cleanup) { return cleanup(); }); }; }, [api, addEventListener]); }; function useViewEvents(handlers) { var ctx = react.useContext(KrpanoContext); if (!ctx) throw new Error("KrpanoContext chưa được cung cấp"); var addEventListener = useKrpanoEventBridge().addEventListener; var api = ctx.api; react.useEffect(function () { if (!api) return; var cleanupFns = []; // onviewchange if (handlers.onviewchange) { api.set("events.onviewchange", "js(krpanoEventBridge(\"onviewchange\", {hlookat:get(view.hlookat), vlookat:get(view.vlookat), fov:get(view.fov)}))"); var cleanup = addEventListener("onviewchange", function (data) { handlers.onviewchange(data); }); cleanupFns.push(cleanup); } // onviewchanged if (handlers.onviewchanged) { api.set("events.onviewchanged", "js(krpanoEventBridge(\"onviewchanged\", {hlookat:get(view.hlookat), vlookat:get(view.vlookat), fov:get(view.fov)}))"); var cleanup = addEventListener("onviewchanged", function (data) { handlers.onviewchanged(data); }); cleanupFns.push(cleanup); } // onidle if (handlers.onidle) { api.set("events.onidle", "js(krpanoEventBridge(\"onidle\", {}))"); var cleanup = addEventListener("onidle", function (data) { handlers.onidle(data); }); cleanupFns.push(cleanup); } return function () { cleanupFns.forEach(function (fn) { return fn(); }); }; }, [api, handlers, addEventListener]); } function useSceneEvents(handlers) { var ctx = react.useContext(KrpanoContext); if (!ctx) throw new Error("KrpanoContext chưa được cung cấp"); var addEventListener = useKrpanoEventBridge().addEventListener; var api = ctx.api; react.useEffect(function () { if (!api) return; var cleanupFns = []; // onstart if (handlers.onstart) { api.set("events.onstart", "js(krpanoEventBridge(\"onstart\", {scene:get(xml.scene)}))"); var cleanup = addEventListener("onstart", function (data) { handlers.onstart(data); }); cleanupFns.push(cleanup); } // onloaded if (handlers.onloaded) { api.set("events.onloaded", "js(krpanoEventBridge(\"onloaded\", {scene:get(xml.scene)}))"); var cleanup = addEventListener("onloaded", function (data) { handlers.onloaded(data); }); cleanupFns.push(cleanup); } // onready if (handlers.onready) { api.set("events.onready", "js(krpanoEventBridge(\"onready\", {scene:get(xml.scene)}))"); var cleanup = addEventListener("onready", function (data) { handlers.onready(data); }); cleanupFns.push(cleanup); } // onxmlcomplete if (handlers.onxmlcomplete) { api.set("events.onxmlcomplete", "js(krpanoEventBridge(\"onxmlcomplete\", {scene:get(xml.scene)}))"); var cleanup = addEventListener("onxmlcomplete", function (data) { handlers.onxmlcomplete(data); }); cleanupFns.push(cleanup); } // onnewpano if (handlers.onnewpano) { api.set("events.onnewpano", "js(krpanoEventBridge(\"onnewpano\", {scene:get(xml.scene)}))"); var cleanup = addEventListener("onnewpano", function (data) { handlers.onnewpano(data); }); cleanupFns.push(cleanup); } // onremovepano if (handlers.onremovepano) { api.set("events.onremovepano", "js(krpanoEventBridge(\"onremovepano\", {scene:get(xml.scene)}))"); var cleanup = addEventListener("onremovepano", function (data) { handlers.onremovepano(data); }); cleanupFns.push(cleanup); } return function () { cleanupFns.forEach(function (fn) { return fn(); }); }; }, [api, handlers, addEventListener]); } function useMouseEvents(handlers) { var ctx = react.useContext(KrpanoContext); if (!ctx) throw new Error("KrpanoContext chưa được cung cấp"); var addEventListener = useKrpanoEventBridge().addEventListener; var api = ctx.api; react.useEffect(function () { if (!api) return; var cleanupFns = []; // onclick if (handlers.onclick) { api.set("events.onclick", "js(krpanoEventBridge(\"onclick\", {stagex:get(mouse.stagex), stagey:get(mouse.stagey)}))"); var cleanup = addEventListener("onclick", function (data) { handlers.onclick(data); }); cleanupFns.push(cleanup); } // ondblclick if (handlers.ondblclick) { api.set("events.ondblclick", "js(krpanoEventBridge(\"ondblclick\", {stagex:get(mouse.stagex), stagey:get(mouse.stagey)}))"); var cleanup = addEventListener("ondblclick", function (data) { handlers.ondblclick(data); }); cleanupFns.push(cleanup); } // onmousedown if (handlers.onmousedown) { api.set("events.onmousedown", "js(krpanoEventBridge(\"onmousedown\", {stagex:get(mouse.stagex), stagey:get(mouse.stagey), button:get(mouse.button)}))"); var cleanup = addEventListener("onmousedown", function (data) { handlers.onmousedown(data); }); cleanupFns.push(cleanup); } // onmouseup if (handlers.onmouseup) { api.set("events.onmouseup", "js(krpanoEventBridge(\"onmouseup\", {stagex:get(mouse.stagex), stagey:get(mouse.stagey), button:get(mouse.button)}))"); var cleanup = addEventListener("onmouseup", function (data) { handlers.onmouseup(data); }); cleanupFns.push(cleanup); } // onmousemove if (handlers.onmousemove) { api.set("events.onmousemove", "js(krpanoEventBridge(\"onmousemove\", {stagex:get(mouse.stagex), stagey:get(mouse.stagey)}))"); var cleanup = addEventListener("onmousemove", function (data) { handlers.onmousemove(data); }); cleanupFns.push(cleanup); } return function () { cleanupFns.forEach(function (fn) { return fn(); }); }; }, [api, handlers, addEventListener]); } function useSystemEvents(handlers) { var ctx = react.useContext(KrpanoContext); if (!ctx) throw new Error("KrpanoContext chưa được cung cấp"); var api = ctx.api; var addEventListener = useKrpanoEventBridge().addEventListener; react.useEffect(function () { if (!api) return; var cleanupFns = []; // helper để đăng ký var register = function register(event, expr) { if (handlers[event]) { api.set("events.".concat(event), expr); var cleanup = addEventListener(event, function (data) { var _a, _b; switch (event) { case "onkeydown": case "onkeyup": (_a = handlers[event]) === null || _a === void 0 ? void 0 : _a.call(handlers, data); break; case "onready": case "onxmlcomplete": case "onloadcomplete": case "onnewpano": case "onremovepano": case "onresize": case "onenterfullscreen": case "onexitfullscreen": (_b = handlers[event]) === null || _b === void 0 ? void 0 : _b.call(handlers, data); break; } }); cleanupFns.push(cleanup); } }; // đăng ký các event register("onready", "js(krpanoEventBridge(\"onready\", {}))"); register("onxmlcomplete", "js(krpanoEventBridge(\"onxmlcomplete\", {}))"); register("onnewpano", "js(krpanoEventBridge(\"onnewpano\", {scene:get(xml.scene)}))"); register("onremovepano", "js(krpanoEventBridge(\"onremovepano\", {}))"); register("onkeydown", "js(krpanoEventBridge(\"onkeydown\", {keycode:get(keycode)}))"); register("onkeyup", "js(krpanoEventBridge(\"onkeyup\", {keycode:get(keycode)}))"); register("onresize", "js(krpanoEventBridge(\"onresize\", {}))"); register("onenterfullscreen", "js(krpanoEventBridge(\"onenterfullscreen\", {}))"); register("onexitfullscreen", "js(krpanoEventBridge(\"onexitfullscreen\", {}))"); return function () { return cleanupFns.forEach(function (fn) { return fn(); }); }; }, [api, handlers, addEventListener]); } function useKeyboardEvents(handlers) { var ctx = react.useContext(KrpanoContext); if (!ctx) throw new Error("KrpanoContext chưa được cung cấp"); var addEventListener = useKrpanoEventBridge().addEventListener; var api = ctx.api; react.useEffect(function () { if (!api) return; var cleanupFns = []; // onkeydown if (handlers.onkeydown) { api.set("events.onkeydown", "js(krpanoEventBridge(\"onkeydown\", {\n keycode:get(key.keycode),\n key:get(key.key),\n ctrlkey:get(key.ctrlkey),\n shiftkey:get(key.shiftkey),\n altkey:get(key.altkey)\n }))"); var cleanup = addEventListener("onkeydown", function (data) { handlers.onkeydown(data); }); cleanupFns.push(cleanup); } // onkeyup if (handlers.onkeyup) { api.set("events.onkeyup", "js(krpanoEventBridge(\"onkeyup\", {\n keycode:get(key.keycode),\n key:get(key.key),\n ctrlkey:get(key.ctrlkey),\n shiftkey:get(key.shiftkey),\n altkey:get(key.altkey)\n }))"); var cleanup = addEventListener("onkeyup", function (data) { handlers.onkeyup(data); }); cleanupFns.push(cleanup); } return function () { cleanupFns.forEach(function (fn) { return fn(); }); }; }, [api, handlers, addEventListener]); } /** * Hook để lắng nghe events từ Krpano */ function useKrpanoEventListener() { var ctx = react.useContext(KrpanoContext); if (!ctx) throw new Error("KrpanoContext chưa được cung cấp"); var api = ctx.api; var handlersRef = react.useRef(new Map()); // Khởi tạo global event bridge react.useEffect(function () { if (typeof window !== 'undefined') { // Tạo global bridge function để Krpano có thể gọi window.krpanoEventBridge = function (eventType, eventData) { var handlers = handlersRef.current.get(eventType); if (handlers) { var processedData_1 = __assign({ type: eventType, timestamp: Date.now() }, eventData); handlers.forEach(function (handler) { try { handler(processedData_1); } catch (error) { console.error("Error in Krpano event handler for ".concat(eventType, ":"), error); } }); } }; // Setup Krpano event listeners setupKrpanoEventListeners(); } return function () { if (typeof window !== 'undefined') { delete window.krpanoEventBridge; } }; }, [api]); // Setup các event listeners trong Krpano var setupKrpanoEventListeners = react.useCallback(function () { if (!api) return; // System Events api.set('events.onloadcomplete', 'js(krpanoEventBridge("onloadcomplete", {}))'); api.set('events.onxmlcomplete', 'js(krpanoEventBridge("onxmlcomplete", {}))'); api.set('events.onready', 'js(krpanoEventBridge("onready", {}))'); api.set('events.onerror', 'js(krpanoEventBridge("onerror", {error: get(lasterror)}))'); api.set('events.onresize', 'js(krpanoEventBridge("onresize", {width: get(stagewidth), height: get(stageheight)}))'); api.set('events.onfullscreenchange', 'js(krpanoEventBridge("onfullscreenchange", {fullscreen: get(fullscreen)}))'); // Scene Events api.set('events.onnewscene', 'js(krpanoEventBridge("onnewscene", {scenename: get(xml.scene), sceneindex: get(xml.sceneindex)}))'); api.set('events.onremovescene', 'js(krpanoEventBridge("onremovescene", {scenename: get(xml.scene)}))'); // View Events api.set('events.onviewchange', 'js(krpanoEventBridge("onviewchange", {hlookat: get(view.hlookat), vlookat: get(view.vlookat), fov: get(view.fov)}))'); api.set('events.onviewchanged', 'js(krpanoEventBridge("onviewchanged", {hlookat: get(view.hlookat), vlookat: get(view.vlookat), fov: get(view.fov)}))'); api.set('events.onidle', 'js(krpanoEventBridge("onidle", {}))'); // Mouse Events api.set('events.onclick', 'js(krpanoEventBridge("onclick", {stagex: get(mouse.stagex), stagey: get(mouse.stagey)}))'); api.set('events.ondblclick', 'js(krpanoEventBridge("ondblclick", {stagex: get(mouse.stagex), stagey: get(mouse.stagey)}))'); api.set('events.onmousedown', 'js(krpanoEventBridge("onmousedown", {stagex: get(mouse.stagex), stagey: get(mouse.stagey), button: get(mouse.button)}))'); api.set('events.onmouseup', 'js(krpanoEventBridge("onmouseup", {stagex: get(mouse.stagex), stagey: get(mouse.stagey), button: get(mouse.button)}))'); api.set('events.onmousemove', 'js(krpanoEventBridge("onmousemove", {stagex: get(mouse.stagex), stagey: get(mouse.stagey)}))'); // Keyboard Events api.set('events.onkeydown', 'js(krpanoEventBridge("onkeydown", {keycode: get(key.keycode), key: get(key.key), ctrlkey: get(key.ctrlkey), shiftkey: get(key.shiftkey), altkey: get(key.altkey)}))'); api.set('events.onkeyup', 'js(krpanoEventBridge("onkeyup", {keycode: get(key.keycode), key: get(key.key), ctrlkey: get(key.ctrlkey), shiftkey: get(key.shiftkey), altkey: get(key.altkey)}))'); }, [api]); // Thêm event listener var addEventListener = react.useCallback(function (eventType, handler) { var _a; if (!handlersRef.current.has(eventType)) { handlersRef.current.set(eventType, new Set()); } (_a = handlersRef.current.get(eventType)) === null || _a === void 0 ? void 0 : _a.add(handler); // Return cleanup function return function () { var _a; (_a = handlersRef.current.get(eventType)) === null || _a === void 0 ? void 0 : _a["delete"](handler); }; }, []); // Xóa event listener var removeEventListener = react.useCallback(function (eventType, handler) { var _a; (_a = handlersRef.current.get(eventType)) === null || _a === void 0 ? void 0 : _a["delete"](handler); }, []); // Xóa tất cả listeners của một event var removeAllEventListeners = react.useCallback(function (eventType) { if (eventType) { handlersRef.current["delete"](eventType); } else { handlersRef.current.clear(); } }, []); // Trigger custom event var triggerCustomEvent = react.useCallback(function (eventType, data) { if (typeof window !== 'undefined' && window.krpanoEventBridge) { window.krpanoEventBridge(eventType, data); } }, []); // Register multiple event handlers at once var registerEventHandlers = react.useCallback(function (handlers) { var cleanupFunctions = []; Object.entries(handlers).forEach(function (_a) { var eventType = _a[0], handler = _a[1]; if (handler) { var cleanup = addEventListener(eventType, handler); cleanupFunctions.push(cleanup); } }); // Return cleanup function for all handlers return function () { cleanupFunctions.forEach(function (cleanup) { return cleanup(); }); }; }, [addEventListener]); // Setup hotspot event handler dynamically var setupHotspotEvents = react.useCallback(function (hotspotName, handlers) { if (!api) return function () {}; var cleanupFunctions = []; if (handlers.onclick) { api.set("hotspot[".concat(hotspotName, "].onclick"), "js(krpanoEventBridge(\"onhotspotclick\", {hotspotname: \"".concat(hotspotName, "\", stagex: get(mouse.stagex), stagey: get(mouse.stagey), ath: get(hotspot[").concat(hotspotName, "].ath), atv: get(hotspot[").concat(hotspotName, "].atv)}))")); var cleanup = addEventListener('onhotspotclick', function (data) { if ('hotspotname' in data && data.hotspotname === hotspotName) { handlers.onclick(data); } }); cleanupFunctions.push(cleanup); } if (handlers.onover) { api.set("hotspot[".concat(hotspotName, "].onover"), "js(krpanoEventBridge(\"onhotspotover\", {hotspotname: \"".concat(hotspotName, "\", ath: get(hotspot[").concat(hotspotName, "].ath), atv: get(hotspot[").concat(hotspotName, "].atv)}))")); var cleanup = addEventListener('onhotspotover', function (data) { if ('hotspotname' in data && data.hotspotname === hotspotName) { handlers.onover(data); } }); cleanupFunctions.push(cleanup); } if (handlers.onout) { api.set("hotspot[".concat(hotspotName, "].onout"), "js(krpanoEventBridge(\"onhotspotout\", {hotspotname: \"".concat(hotspotName, "\"}))")); var cleanup = addEventListener('onhotspotout', function (data) { if ('hotspotname' in data && data.hotspotname === hotspotName) { handlers.onout(data); } }); cleanupFunctions.push(cleanup); } return function () { cleanupFunctions.forEach(function (cleanup) { return cleanup(); }); }; }, [api, addEventListener]); // Setup layer event handler dynamically var setupLayerEvents = react.useCallback(function (layerName, handlers) { if (!api) return function () {}; var cleanupFunctions = []; if (handlers.onclick) { api.set("layer[".concat(layerName, "].onclick"), "js(krpanoEventBridge(\"onlayerclick\", {layername: \"".concat(layerName, "\", stagex: get(mouse.stagex), stagey: get(mouse.stagey)}))")); var cleanup = addEventListener('onlayerclick', function (data) { if ('layername' in data && data.layername === layerName) { handlers.onclick(data); } }); cleanupFunctions.push(cleanup); } if (handlers.onover) { api.set("layer[".concat(layerName, "].onover"), "js(krpanoEventBridge(\"onlayerover\", {layername: \"".concat(layerName, "\"}))")); var cleanup = addEventListener('onlayerover', function (data) { if ('layername' in data && data.layername === layerName) { handlers.onover(data); } }); cleanupFunctions.push(cleanup); } if (handlers.onout) { api.set("layer[".concat(layerName, "].onout"), "js(krpanoEventBridge(\"onlayerout\", {layername: \"".concat(layerName, "\"}))")); var cleanup = addEventListener('onlayerout', function (data) { if ('layername' in data && data.layername === layerName) { handlers.onout(data); } }); cleanupFunctions.push(cleanup); } return function () { cleanupFunctions.forEach(function (cleanup) { return cleanup(); }); }; }, [api, addEventListener]); return { // Core functions addEventListener: addEventListener, removeEventListener: removeEventListener, removeAllEventListeners: removeAllEventListeners, registerEventHandlers: registerEventHandlers, triggerCustomEvent: triggerCustomEvent, // Specialized functions setupHotspotEvents: setupHotspotEvents, setupLayerEvents: setupLayerEvents, // Utility functions isListening: function isListening(eventType) { return handlersRef.current.has(eventType); }, getListenerCount: function getListenerCount(eventType) { var _a; return ((_a = handlersRef.current.get(eventType)) === null || _a === void 0 ? void 0 : _a.size) || 0; }, getAllListeners: function getAllListeners() { return Array.from(handlersRef.current.keys()); } }; } var KrpanoViewer = function KrpanoViewer(_a) { var path = _a.path, _b = _a.xmlName, xmlName = _b === void 0 ? "tour.xml" : _b, _c = _a.jsN