", a);
if (b === -1) {
return null;
}
const data = binary.substring(a, b);
if (!data.includes("GPano:")) {
return null;
}
return {
fullWidth: getXMPValue(data, "FullPanoWidthPixels"),
fullHeight: getXMPValue(data, "FullPanoHeightPixels"),
croppedWidth: getXMPValue(data, "CroppedAreaImageWidthPixels"),
croppedHeight: getXMPValue(data, "CroppedAreaImageHeightPixels"),
croppedX: getXMPValue(data, "CroppedAreaLeftPixels"),
croppedY: getXMPValue(data, "CroppedAreaTopPixels"),
poseHeading: getXMPValue(data, "PoseHeadingDegrees", false),
posePitch: getXMPValue(data, "PosePitchDegrees", false),
poseRoll: getXMPValue(data, "PoseRollDegrees", false),
initialHeading: getXMPValue(data, "InitialViewHeadingDegrees", false),
initialPitch: getXMPValue(data, "InitialViewPitchDegrees", false),
initialFov: getXMPValue(data, "InitialHorizontalFOVDegrees", false)
};
}
/**
* Reads a Blob as a string
*/
loadBlobAsString(blob) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = reject;
reader.readAsText(blob);
});
}
/**
* Creates the final texture from image and panorama data
*/
createEquirectangularTexture(img) {
if (this.config.blur || img.width > SYSTEM.maxTextureWidth) {
const ratio = Math.min(1, SYSTEM.maxCanvasWidth / img.width);
const buffer = new OffscreenCanvas(Math.floor(img.width * ratio), Math.floor(img.height * ratio));
const ctx = buffer.getContext("2d");
if (this.config.blur) {
ctx.filter = `blur(${buffer.width / 2048}px)`;
}
ctx.drawImage(img, 0, 0, buffer.width, buffer.height);
return createTexture(buffer);
}
return createTexture(img);
}
createMesh(panoData) {
const hStart = panoData.croppedX / panoData.fullWidth * 2 * Math.PI;
const hLength = panoData.croppedWidth / panoData.fullWidth * 2 * Math.PI;
const vStart = panoData.croppedY / panoData.fullHeight * Math.PI;
const vLength = panoData.croppedHeight / panoData.fullHeight * Math.PI;
const geometry = new import_three4.SphereGeometry(
SPHERE_RADIUS,
Math.round(this.SPHERE_SEGMENTS / (2 * Math.PI) * hLength),
Math.round(this.SPHERE_HORIZONTAL_SEGMENTS / Math.PI * vLength),
-Math.PI / 2 + hStart,
hLength,
vStart,
vLength
).scale(-1, 1, 1);
const material = new import_three4.MeshBasicMaterial({ depthTest: false, depthWrite: false });
return new import_three4.Mesh(geometry, material);
}
setTexture(mesh, textureData) {
mesh.material.map = textureData.texture;
}
setTextureOpacity(mesh, opacity) {
mesh.material.opacity = opacity;
mesh.material.transparent = opacity < 1;
}
disposeTexture({ texture }) {
texture.dispose();
}
disposeMesh(mesh) {
mesh.geometry.dispose();
mesh.material.dispose();
}
};
EquirectangularAdapter.id = "equirectangular";
EquirectangularAdapter.VERSION = "5.11.1";
EquirectangularAdapter.supportsDownload = true;
// src/adapters/DualFisheyeAdapter.ts
var DualFisheyeAdapter = class extends EquirectangularAdapter {
constructor(viewer, config) {
super(viewer, {
resolution: config?.resolution ?? 64,
useXmpData: false
});
}
async loadTexture(panorama, loader) {
const result = await super.loadTexture(panorama, loader, null, false);
result.panoData = null;
return result;
}
createMesh() {
const geometry = new import_three5.SphereGeometry(
SPHERE_RADIUS,
this.SPHERE_SEGMENTS,
this.SPHERE_HORIZONTAL_SEGMENTS
).scale(-1, 1, 1).toNonIndexed();
const uvs = geometry.getAttribute("uv");
const normals = geometry.getAttribute("normal");
for (let i = 0; i < uvs.count; i++) {
for (let j = 0; j < 3; j++) {
const index = i * 3 + j;
const x = normals.getX(index);
const y = normals.getY(index);
const z = normals.getZ(index);
const c = 0.947;
if (i < uvs.count / 6) {
const correction = x === 0 && z === 0 ? 1 : Math.acos(y) / Math.sqrt(x * x + z * z) * (2 / Math.PI);
uvs.setXY(
index,
x * (c / 4) * correction + 1 / 4,
z * (c / 2) * correction + 1 / 2
);
} else {
const correction = x === 0 && z === 0 ? 1 : Math.acos(-y) / Math.sqrt(x * x + z * z) * (2 / Math.PI);
uvs.setXY(
index,
-x * (c / 4) * correction + 3 / 4,
z * (c / 2) * correction + 1 / 2
);
}
}
}
geometry.rotateX(-Math.PI / 2);
geometry.rotateY(Math.PI);
const material = new import_three5.MeshBasicMaterial({ depthTest: false, depthWrite: false });
return new import_three5.Mesh(geometry, material);
}
};
DualFisheyeAdapter.id = "dual-fisheye";
DualFisheyeAdapter.VERSION = "5.11.1";
// src/components/AbstractComponent.ts
var AbstractComponent = class _AbstractComponent {
constructor(parent, config) {
this.parent = parent;
/**
* All child components
* @internal
*/
this.children = [];
/**
* Container element
*/
this.container = document.createElement("div");
/**
* Internal properties
* @internal
*/
this.state = {
visible: true
};
this.viewer = parent instanceof _AbstractComponent ? parent.viewer : parent;
this.container.className = config.className || "";
this.parent.children.push(this);
this.parent.container.appendChild(this.container);
}
/**
* Destroys the component
*/
destroy() {
this.parent.container.removeChild(this.container);
const childIdx = this.parent.children.indexOf(this);
if (childIdx !== -1) {
this.parent.children.splice(childIdx, 1);
}
this.children.slice().forEach((child) => child.destroy());
this.children.length = 0;
}
/**
* Displays or hides the component
*/
toggle(visible = !this.isVisible()) {
if (!visible) {
this.hide();
} else {
this.show();
}
}
/**
* Hides the component
*/
// @ts-ignore unused parameter
// eslint-disable-next-line @typescript-eslint/no-unused-vars
hide(options) {
this.container.style.display = "none";
this.state.visible = false;
}
/**
* Displays the component
*/
// @ts-ignore unused parameter
// eslint-disable-next-line @typescript-eslint/no-unused-vars
show(options) {
this.container.style.display = "";
this.state.visible = true;
}
/**
* Checks if the component is visible
*/
isVisible() {
return this.state.visible;
}
};
// src/buttons/AbstractButton.ts
var getConfig2 = getConfigParser({
id: null,
className: null,
title: null,
hoverScale: false,
collapsable: false,
tabbable: true,
icon: null,
iconActive: null
});
var AbstractButton = class extends AbstractComponent {
constructor(navbar, config) {
super(navbar, {
className: `psv-button ${config.hoverScale ? "psv-button--hover-scale" : ""} ${config.className || ""}`
});
/**
* Internal properties
*/
this.state = {
visible: true,
enabled: true,
supported: true,
collapsed: false,
active: false,
width: 0
};
this.config = getConfig2(config);
if (!config.id) {
this.config.id = this.constructor.id;
}
if (config.icon) {
this.__setIcon(config.icon);
}
this.state.width = this.container.offsetWidth;
if (this.config.title) {
this.container.title = this.viewer.config.lang[this.config.title] ?? this.config.title;
} else if (this.id && this.id in this.viewer.config.lang) {
this.container.title = this.viewer.config.lang[this.id];
}
if (config.tabbable) {
this.container.tabIndex = 0;
}
this.container.addEventListener("click", (e) => {
if (this.state.enabled) {
this.onClick();
}
e.stopPropagation();
});
this.container.addEventListener("keydown", (e) => {
if (e.key === KEY_CODES.Enter && this.state.enabled) {
this.onClick();
e.stopPropagation();
}
});
}
get id() {
return this.config.id;
}
get title() {
return this.container.title;
}
get content() {
return this.container.innerHTML;
}
get width() {
return this.state.width;
}
get collapsable() {
return this.config.collapsable;
}
show(refresh = true) {
if (!this.isVisible()) {
this.state.visible = true;
if (!this.state.collapsed) {
this.container.style.display = "";
}
if (refresh) {
this.viewer.navbar.autoSize();
}
}
}
hide(refresh = true) {
if (this.isVisible()) {
this.state.visible = false;
this.container.style.display = "none";
if (refresh) {
this.viewer.navbar.autoSize();
}
}
}
/**
* Hides/shows the button depending of the result of {@link isSupported}
* @internal
*/
checkSupported() {
resolveBoolean(this.isSupported(), (supported, init) => {
if (!this.state) {
return;
}
this.state.supported = supported;
if (!init) {
this.toggle(supported);
} else if (!supported) {
this.hide();
}
});
}
/**
* Perform action when the navbar size/content changes
* @internal
*/
autoSize() {
}
/**
* Checks if the button can be displayed
*/
isSupported() {
return true;
}
/**
* Changes the active state of the button
*/
toggleActive(active = !this.state.active) {
if (active !== this.state.active) {
this.state.active = active;
toggleClass(this.container, "psv-button--active", this.state.active);
if (this.config.iconActive) {
this.__setIcon(this.state.active ? this.config.iconActive : this.config.icon);
}
}
}
/**
* Disables the button
*/
disable() {
this.container.classList.add("psv-button--disabled");
this.state.enabled = false;
}
/**
* Enables the button
*/
enable() {
this.container.classList.remove("psv-button--disabled");
this.state.enabled = true;
}
/**
* Collapses the button in the navbar menu
*/
collapse() {
this.state.collapsed = true;
this.container.style.display = "none";
}
/**
* Uncollapses the button from the navbar menu
*/
uncollapse() {
this.state.collapsed = false;
if (this.state.visible) {
this.container.style.display = "";
}
}
__setIcon(icon) {
this.container.innerHTML = icon;
addClasses(this.container.querySelector("svg"), "psv-button-svg");
}
};
// src/buttons/CustomButton.ts
var CustomButton = class extends AbstractButton {
constructor(navbar, config) {
super(navbar, {
id: config.id ?? `psvButton-${Math.random().toString(36).substring(2)}`,
className: `psv-custom-button ${config.className || ""}`,
hoverScale: false,
collapsable: config.collapsable !== false,
tabbable: config.tabbable !== false,
title: config.title
});
this.customOnClick = config.onClick;
if (config.content) {
if (typeof config.content === "string") {
this.container.innerHTML = config.content;
} else {
this.container.classList.add("psv-custom-button--no-padding");
config.content.style.height = "100%";
config.content.attachViewer?.(this.viewer);
this.container.appendChild(config.content);
}
}
this.state.width = this.container.offsetWidth;
if (config.disabled) {
this.disable();
}
if (config.visible === false) {
this.hide();
}
}
onClick() {
this.customOnClick?.(this.viewer);
}
};
// src/buttons/DescriptionButton.ts
var DescriptionButton = class extends AbstractButton {
constructor(navbar) {
super(navbar, {
className: "psv-description-button",
hoverScale: true,
collapsable: false,
tabbable: true,
icon: ICONS.info
});
this.mode = 0 /* NONE */;
this.viewer.addEventListener(HideNotificationEvent.type, this);
this.viewer.addEventListener(ShowNotificationEvent.type, this);
this.viewer.addEventListener(HidePanelEvent.type, this);
this.viewer.addEventListener(ShowPanelEvent.type, this);
this.viewer.addEventListener(ConfigChangedEvent.type, this);
}
destroy() {
this.viewer.removeEventListener(HideNotificationEvent.type, this);
this.viewer.removeEventListener(ShowNotificationEvent.type, this);
this.viewer.removeEventListener(HidePanelEvent.type, this);
this.viewer.removeEventListener(ShowPanelEvent.type, this);
this.viewer.removeEventListener(ConfigChangedEvent.type, this);
super.destroy();
}
handleEvent(e) {
if (e instanceof ConfigChangedEvent) {
e.containsOptions("description") && this.autoSize(true);
return;
}
if (!this.mode) {
return;
}
let closed = false;
if (e instanceof HideNotificationEvent) {
closed = this.mode === 1 /* NOTIF */;
} else if (e instanceof ShowNotificationEvent) {
closed = this.mode === 1 /* NOTIF */ && e.notificationId !== IDS.DESCRIPTION;
} else if (e instanceof HidePanelEvent) {
closed = this.mode === 2 /* PANEL */;
} else if (e instanceof ShowPanelEvent) {
closed = this.mode === 2 /* PANEL */ && e.panelId !== IDS.DESCRIPTION;
}
if (closed) {
this.toggleActive(false);
this.mode = 0 /* NONE */;
}
}
onClick() {
if (this.mode) {
this.__close();
} else {
this.__open();
}
}
hide(refresh) {
super.hide(refresh);
if (this.mode) {
this.__close();
}
}
/**
* This button can only be refreshed from NavbarCaption
* @internal
*/
autoSize(refresh = false) {
if (refresh) {
const caption = this.viewer.navbar.getButton("caption", false);
const captionHidden = caption && !caption.isVisible();
const hasDescription = !!this.viewer.config.description;
if (captionHidden || hasDescription) {
this.show(false);
} else {
this.hide(false);
}
}
}
__close() {
switch (this.mode) {
case 1 /* NOTIF */:
this.viewer.notification.hide(IDS.DESCRIPTION);
break;
case 2 /* PANEL */:
this.viewer.panel.hide(IDS.DESCRIPTION);
break;
default:
}
}
__open() {
this.toggleActive(true);
if (this.viewer.config.description) {
this.mode = 2 /* PANEL */;
this.viewer.panel.show({
id: IDS.DESCRIPTION,
content: (this.viewer.config.caption ? `${this.viewer.config.caption}
` : "") + this.viewer.config.description
});
} else {
this.mode = 1 /* NOTIF */;
this.viewer.notification.show({
id: IDS.DESCRIPTION,
content: this.viewer.config.caption
});
}
}
};
DescriptionButton.id = "description";
// src/buttons/DownloadButton.ts
var DownloadButton = class extends AbstractButton {
constructor(navbar) {
super(navbar, {
className: "psv-download-button",
hoverScale: true,
collapsable: true,
tabbable: true,
icon: ICONS.download
});
this.viewer.addEventListener(ConfigChangedEvent.type, this);
}
destroy() {
this.viewer.removeEventListener(ConfigChangedEvent.type, this);
super.destroy();
}
handleEvent(e) {
if (e instanceof ConfigChangedEvent) {
e.containsOptions("downloadUrl") && this.checkSupported();
}
}
onClick() {
const link = document.createElement("a");
link.href = this.viewer.config.downloadUrl || this.viewer.config.panorama;
if (link.href.startsWith("data:") && !this.viewer.config.downloadName) {
link.download = "panorama." + link.href.substring(0, link.href.indexOf(";")).split("/").pop();
} else {
link.download = this.viewer.config.downloadName || link.href.split("/").pop();
}
link.target = "_blank";
this.viewer.container.appendChild(link);
link.click();
setTimeout(() => {
this.viewer.container.removeChild(link);
}, 100);
}
checkSupported() {
const supported = this.viewer.adapter.constructor.supportsDownload || this.viewer.config.downloadUrl;
if (supported) {
this.show();
} else {
this.hide();
}
}
};
DownloadButton.id = "download";
// src/buttons/FullscreenButton.ts
var FullscreenButton = class extends AbstractButton {
constructor(navbar) {
super(navbar, {
className: "psv-fullscreen-button",
hoverScale: true,
collapsable: false,
tabbable: true,
icon: ICONS.fullscreenIn,
iconActive: ICONS.fullscreenOut
});
this.viewer.addEventListener(FullscreenEvent.type, this);
}
destroy() {
this.viewer.removeEventListener(FullscreenEvent.type, this);
super.destroy();
}
handleEvent(e) {
if (e instanceof FullscreenEvent) {
this.toggleActive(e.fullscreenEnabled);
}
}
onClick() {
this.viewer.toggleFullscreen();
}
};
FullscreenButton.id = "fullscreen";
// src/buttons/MenuButton.ts
var BUTTON_DATA = "psvButton";
var MENU_TEMPLATE = (buttons, title) => `
`;
var MenuButton = class extends AbstractButton {
constructor(navbar) {
super(navbar, {
className: "psv-menu-button",
hoverScale: true,
collapsable: false,
tabbable: true,
icon: ICONS.menu
});
this.viewer.addEventListener(ShowPanelEvent.type, this);
this.viewer.addEventListener(HidePanelEvent.type, this);
super.hide();
}
destroy() {
this.viewer.removeEventListener(ShowPanelEvent.type, this);
this.viewer.removeEventListener(HidePanelEvent.type, this);
super.destroy();
}
handleEvent(e) {
if (e instanceof ShowPanelEvent) {
this.toggleActive(e.panelId === IDS.MENU);
} else if (e instanceof HidePanelEvent) {
this.toggleActive(false);
}
}
onClick() {
if (this.state.active) {
this.__hideMenu();
} else {
this.__showMenu();
}
}
hide(refresh) {
super.hide(refresh);
this.__hideMenu();
}
show(refresh) {
super.show(refresh);
if (this.state.active) {
this.__showMenu();
}
}
__showMenu() {
this.viewer.panel.show({
id: IDS.MENU,
content: MENU_TEMPLATE(this.viewer.navbar.collapsed, this.viewer.config.lang.menu),
noMargin: true,
clickHandler: (target) => {
const li = target ? getClosest(target, ".psv-panel-menu-item") : void 0;
const buttonId = li ? li.dataset[BUTTON_DATA] : void 0;
if (buttonId) {
this.viewer.navbar.getButton(buttonId).onClick();
this.__hideMenu();
}
}
});
}
__hideMenu() {
this.viewer.panel.hide(IDS.MENU);
}
};
MenuButton.id = "menu";
// src/buttons/AbstractMoveButton.ts
function getIcon(value) {
let angle2 = 0;
switch (value) {
case 0 /* UP */:
angle2 = 90;
break;
case 1 /* DOWN */:
angle2 = -90;
break;
case 3 /* RIGHT */:
angle2 = 180;
break;
default:
angle2 = 0;
break;
}
return ICONS.arrow.replace("rotate(0", `rotate(${angle2}`);
}
var AbstractMoveButton = class extends AbstractButton {
constructor(navbar, direction) {
super(navbar, {
className: "psv-move-button",
hoverScale: true,
collapsable: false,
tabbable: true,
icon: getIcon(direction)
});
this.direction = direction;
this.handler = new PressHandler();
this.container.addEventListener("mousedown", this);
this.container.addEventListener("keydown", this);
this.container.addEventListener("keyup", this);
this.viewer.container.addEventListener("mouseup", this);
this.viewer.container.addEventListener("touchend", this);
}
destroy() {
this.__onMouseUp();
this.viewer.container.removeEventListener("mouseup", this);
this.viewer.container.removeEventListener("touchend", this);
super.destroy();
}
handleEvent(e) {
switch (e.type) {
case "mousedown":
this.__onMouseDown();
break;
case "mouseup":
this.__onMouseUp();
break;
case "touchend":
this.__onMouseUp();
break;
case "keydown":
e.key === KEY_CODES.Enter && this.__onMouseDown();
break;
case "keyup":
e.key === KEY_CODES.Enter && this.__onMouseUp();
break;
}
}
onClick() {
}
isSupported() {
return invertResolvableBoolean(SYSTEM.isTouchEnabled);
}
__onMouseDown() {
if (!this.state.enabled) {
return;
}
const dynamicRoll = {};
switch (this.direction) {
case 0 /* UP */:
dynamicRoll.pitch = false;
break;
case 1 /* DOWN */:
dynamicRoll.pitch = true;
break;
case 3 /* RIGHT */:
dynamicRoll.yaw = false;
break;
default:
dynamicRoll.yaw = true;
break;
}
this.viewer.stopAll();
this.viewer.dynamics.position.roll(dynamicRoll);
this.handler.down();
}
__onMouseUp() {
if (!this.state.enabled) {
return;
}
this.handler.up(() => {
this.viewer.dynamics.position.stop();
this.viewer.resetIdleTimer();
});
}
};
AbstractMoveButton.groupId = "move";
// src/buttons/MoveDownButton.ts
var MoveDownButton = class extends AbstractMoveButton {
constructor(navbar) {
super(navbar, 1 /* DOWN */);
}
};
MoveDownButton.id = "moveDown";
// src/buttons/MoveLeftButton.ts
var MoveLeftButton = class extends AbstractMoveButton {
constructor(navbar) {
super(navbar, 2 /* LEFT */);
}
};
MoveLeftButton.id = "moveLeft";
// src/buttons/MoveRightButton.ts
var MoveRightButton = class extends AbstractMoveButton {
constructor(navbar) {
super(navbar, 3 /* RIGHT */);
}
};
MoveRightButton.id = "moveRight";
// src/buttons/MoveUpButton.ts
var MoveUpButton = class extends AbstractMoveButton {
constructor(navbar) {
super(navbar, 0 /* UP */);
}
};
MoveUpButton.id = "moveUp";
// src/buttons/AbstractZoomButton.ts
var AbstractZoomButton = class extends AbstractButton {
constructor(navbar, icon, direction) {
super(navbar, {
className: "psv-zoom-button",
hoverScale: true,
collapsable: false,
tabbable: true,
icon
});
this.direction = direction;
this.handler = new PressHandler();
this.container.addEventListener("mousedown", this);
this.container.addEventListener("keydown", this);
this.container.addEventListener("keyup", this);
this.viewer.container.addEventListener("mouseup", this);
this.viewer.container.addEventListener("touchend", this);
}
destroy() {
this.__onMouseUp();
this.viewer.container.removeEventListener("mouseup", this);
this.viewer.container.removeEventListener("touchend", this);
super.destroy();
}
handleEvent(e) {
switch (e.type) {
case "mousedown":
this.__onMouseDown();
break;
case "mouseup":
this.__onMouseUp();
break;
case "touchend":
this.__onMouseUp();
break;
case "keydown":
e.key === KEY_CODES.Enter && this.__onMouseDown();
break;
case "keyup":
e.key === KEY_CODES.Enter && this.__onMouseUp();
break;
}
}
onClick() {
}
isSupported() {
return invertResolvableBoolean(SYSTEM.isTouchEnabled);
}
__onMouseDown() {
if (!this.state.enabled) {
return;
}
this.viewer.dynamics.zoom.roll(this.direction === 1 /* OUT */);
this.handler.down();
}
__onMouseUp() {
if (!this.state.enabled) {
return;
}
this.handler.up(() => this.viewer.dynamics.zoom.stop());
}
};
AbstractZoomButton.groupId = "zoom";
// src/buttons/ZoomInButton.ts
var ZoomInButton = class extends AbstractZoomButton {
constructor(navbar) {
super(navbar, ICONS.zoomIn, 0 /* IN */);
}
};
ZoomInButton.id = "zoomIn";
// src/buttons/ZoomOutButton.ts
var ZoomOutButton = class extends AbstractZoomButton {
constructor(navbar) {
super(navbar, ICONS.zoomOut, 1 /* OUT */);
}
};
ZoomOutButton.id = "zoomOut";
// src/buttons/ZoomRangeButton.ts
var ZoomRangeButton = class extends AbstractButton {
constructor(navbar) {
super(navbar, {
className: "psv-zoom-range",
hoverScale: false,
collapsable: false,
tabbable: false
});
this.zoomRange = document.createElement("div");
this.zoomRange.className = "psv-zoom-range-line";
this.container.appendChild(this.zoomRange);
this.zoomValue = document.createElement("div");
this.zoomValue.className = "psv-zoom-range-handle";
this.zoomRange.appendChild(this.zoomValue);
this.slider = new Slider(this.container, "HORIZONTAL" /* HORIZONTAL */, (data) => this.__onSliderUpdate(data));
this.mediaMinWidth = parseInt(getStyleProperty(this.container, "max-width"), 10);
this.viewer.addEventListener(ZoomUpdatedEvent.type, this);
if (this.viewer.state.ready) {
this.__moveZoomValue(this.viewer.getZoomLevel());
} else {
this.viewer.addEventListener(ReadyEvent.type, this);
}
}
destroy() {
this.slider.destroy();
this.viewer.removeEventListener(ZoomUpdatedEvent.type, this);
this.viewer.removeEventListener(ReadyEvent.type, this);
super.destroy();
}
handleEvent(e) {
if (e instanceof ZoomUpdatedEvent) {
this.__moveZoomValue(e.zoomLevel);
} else if (e instanceof ReadyEvent) {
this.__moveZoomValue(this.viewer.getZoomLevel());
}
}
onClick() {
}
isSupported() {
return invertResolvableBoolean(SYSTEM.isTouchEnabled);
}
autoSize() {
if (this.state.supported) {
if (this.viewer.state.size.width <= this.mediaMinWidth && this.state.visible) {
this.hide(false);
} else if (this.viewer.state.size.width > this.mediaMinWidth && !this.state.visible) {
this.show(false);
}
}
}
__moveZoomValue(level) {
this.zoomValue.style.left = level / 100 * this.zoomRange.offsetWidth - this.zoomValue.offsetWidth / 2 + "px";
}
__onSliderUpdate(data) {
if (data.mousedown) {
this.viewer.zoom(data.value * 100);
}
}
};
ZoomRangeButton.id = "zoomRange";
ZoomRangeButton.groupId = "zoom";
// src/data/config.ts
var import_three6 = require("three");
// src/plugins/AbstractPlugin.ts
var AbstractPlugin = class extends TypedEventTarget {
constructor(viewer) {
super();
this.viewer = viewer;
}
/**
* Initializes the plugin
*/
// eslint-disable-next-line @typescript-eslint/no-empty-function
init() {
}
/**
* Destroys the plugin
*/
// eslint-disable-next-line @typescript-eslint/no-empty-function
destroy() {
}
};
var AbstractConfigurablePlugin = class extends AbstractPlugin {
constructor(viewer, config) {
super(viewer);
this.config = this.constructor.configParser(config);
}
/**
* Update options
*/
setOption(option, value) {
this.setOptions({ [option]: value });
}
/**
* Update options
*/
setOptions(options) {
const rawConfig = {
...this.config,
...options
};
const ctor = this.constructor;
const parser = ctor.configParser;
const readonly = ctor.readonlyOptions;
const id = ctor.id;
for (let [key, value] of Object.entries(options)) {
if (!(key in parser.defaults)) {
logWarn(`${id}: Unknown option "${key}"`);
continue;
}
if (readonly.includes(key)) {
logWarn(`${id}: Option "${key}" cannot be updated`);
continue;
}
if (key in parser.parsers) {
value = parser.parsers[key](value, {
rawConfig,
defValue: parser.defaults[key]
});
}
this.config[key] = value;
}
}
};
AbstractConfigurablePlugin.readonlyOptions = [];
function pluginInterop(plugin) {
if (plugin) {
for (const [, p] of [["_", plugin], ...Object.entries(plugin)]) {
if (p.prototype instanceof AbstractPlugin) {
checkVersion(p.id, p.VERSION, "5.11.1");
return p;
}
}
}
return null;
}
// src/data/config.ts
var DEFAULTS = {
panorama: null,
container: null,
adapter: [EquirectangularAdapter, null],
plugins: [],
caption: null,
description: null,
downloadUrl: null,
downloadName: null,
loadingImg: null,
loadingTxt: "",
// empty string => `lang.loading`
size: null,
fisheye: 0,
minFov: 30,
maxFov: 90,
defaultZoomLvl: 50,
defaultYaw: 0,
defaultPitch: 0,
sphereCorrection: null,
moveSpeed: 1,
zoomSpeed: 1,
moveInertia: 0.8,
mousewheel: true,
mousemove: true,
mousewheelCtrlKey: false,
touchmoveTwoFingers: false,
panoData: null,
requestHeaders: null,
canvasBackground: "#000",
rendererParameters: { alpha: true, antialias: true },
withCredentials: false,
// prettier-ignore
navbar: [
"zoom",
"move",
"download",
"description",
"caption",
"fullscreen"
],
lang: {
zoom: "Zoom",
zoomOut: "Zoom out",
zoomIn: "Zoom in",
moveUp: "Move up",
moveDown: "Move down",
moveLeft: "Move left",
moveRight: "Move right",
description: "Description",
download: "Download",
fullscreen: "Fullscreen",
loading: "Loading...",
menu: "Menu",
close: "Close",
twoFingers: "Use two fingers to navigate",
ctrlZoom: "Use ctrl + scroll to zoom the image",
loadError: "The panorama cannot be loaded",
webglError: "Your browser does not seem to support WebGL"
},
keyboard: "fullscreen",
keyboardActions: {
[KEY_CODES.ArrowUp]: "ROTATE_UP" /* ROTATE_UP */,
[KEY_CODES.ArrowDown]: "ROTATE_DOWN" /* ROTATE_DOWN */,
[KEY_CODES.ArrowRight]: "ROTATE_RIGHT" /* ROTATE_RIGHT */,
[KEY_CODES.ArrowLeft]: "ROTATE_LEFT" /* ROTATE_LEFT */,
[KEY_CODES.PageUp]: "ZOOM_IN" /* ZOOM_IN */,
[KEY_CODES.PageDown]: "ZOOM_OUT" /* ZOOM_OUT */,
[KEY_CODES.Plus]: "ZOOM_IN" /* ZOOM_IN */,
[KEY_CODES.Minus]: "ZOOM_OUT" /* ZOOM_OUT */
}
};
var READONLY_OPTIONS = {
panorama: "Use setPanorama method to change the panorama",
panoData: "Use setPanorama method to change the panorama",
container: "Cannot change viewer container",
adapter: "Cannot change adapter",
plugins: "Cannot change plugins"
};
var CONFIG_PARSERS = {
container: (container) => {
if (!container) {
throw new PSVError("No value given for container.");
}
return container;
},
adapter: (adapter, { defValue }) => {
if (!adapter) {
adapter = defValue;
} else if (Array.isArray(adapter)) {
adapter = [adapterInterop(adapter[0]), adapter[1]];
} else {
adapter = [adapterInterop(adapter), null];
}
if (!adapter[0]) {
throw new PSVError("An undefined value was given for adapter.");
}
if (!adapter[0].id) {
throw new PSVError(`Adapter has no id.`);
}
if (adapter[0].id === "little-planet") {
logWarn("LittlePlanetAdapter support has been removed, use `{ fisheye: 2, maxFov: 130 }` to achieve similar effect.");
adapter = defValue;
}
return adapter;
},
defaultYaw: (defaultYaw) => {
return parseAngle(defaultYaw);
},
defaultPitch: (defaultPitch) => {
return parseAngle(defaultPitch, true);
},
defaultZoomLvl: (defaultZoomLvl) => {
return import_three6.MathUtils.clamp(defaultZoomLvl, 0, 100);
},
minFov: (minFov, { rawConfig }) => {
if (rawConfig.maxFov < minFov) {
logWarn("maxFov cannot be lower than minFov");
minFov = rawConfig.maxFov;
}
return import_three6.MathUtils.clamp(minFov, 1, 179);
},
maxFov: (maxFov, { rawConfig }) => {
if (maxFov < rawConfig.minFov) {
maxFov = rawConfig.minFov;
}
return import_three6.MathUtils.clamp(maxFov, 1, 179);
},
moveInertia: (moveInertia, { defValue }) => {
if (moveInertia === true) {
return defValue;
}
if (moveInertia === false) {
return 0;
}
return moveInertia;
},
lang: (lang) => {
return {
...DEFAULTS.lang,
...lang
};
},
keyboard: (keyboard) => {
if (!keyboard) {
return false;
}
if (typeof keyboard === "object") {
logWarn(`Use keyboardActions to configure the keyboard actions, keyboard option must be either true, false, 'fullscreen' or 'always'`);
return "fullscreen";
}
return keyboard === "always" ? "always" : "fullscreen";
},
keyboardActions: (keyboardActions, { rawConfig }) => {
if (rawConfig.keyboard && typeof rawConfig.keyboard === "object") {
return rawConfig.keyboard;
}
return keyboardActions;
},
fisheye: (fisheye) => {
if (fisheye === true) {
return 1;
} else if (fisheye === false) {
return 0;
}
return fisheye;
},
requestHeaders: (requestHeaders) => {
if (requestHeaders && typeof requestHeaders === "object") {
return () => requestHeaders;
}
if (typeof requestHeaders === "function") {
return requestHeaders;
}
return null;
},
rendererParameters: (rendererParameters, { defValue }) => ({
...rendererParameters,
...defValue
}),
plugins: (plugins) => {
return plugins.map((plugin, i) => {
if (Array.isArray(plugin)) {
plugin = [pluginInterop(plugin[0]), plugin[1]];
} else {
plugin = [pluginInterop(plugin), null];
}
if (!plugin[0]) {
throw new PSVError(`An undefined value was given for plugin ${i}.`);
}
if (!plugin[0].id) {
throw new PSVError(`Plugin ${i} has no id.`);
}
return plugin;
});
},
navbar: (navbar) => {
if (navbar === false) {
return null;
}
if (navbar === true) {
return clone(DEFAULTS.navbar);
}
if (typeof navbar === "string") {
return navbar.split(/[ ,]/);
}
return navbar;
}
};
var getViewerConfig = getConfigParser(DEFAULTS, CONFIG_PARSERS);
// src/components/NavbarCaption.ts
var NavbarCaption = class extends AbstractButton {
constructor(navbar) {
super(navbar, {
className: "psv-caption",
hoverScale: false,
collapsable: false,
tabbable: true
});
this.contentWidth = 0;
this.state.width = 0;
this.contentElt = document.createElement("div");
this.contentElt.className = "psv-caption-content";
this.container.appendChild(this.contentElt);
this.setCaption(this.viewer.config.caption);
}
hide() {
this.contentElt.style.display = "none";
this.state.visible = false;
}
show() {
this.contentElt.style.display = "";
this.state.visible = true;
}
onClick() {
}
/**
* Changes the caption
*/
setCaption(html) {
this.show();
this.contentElt.innerHTML = html ?? "";
if (this.contentElt.innerHTML) {
this.contentWidth = this.contentElt.offsetWidth;
} else {
this.contentWidth = 0;
}
this.autoSize();
}
/**
* Toggles content and icon depending on available space
*/
autoSize() {
this.toggle(this.container.offsetWidth >= this.contentWidth);
this.__refreshButton();
}
__refreshButton() {
this.viewer.navbar.getButton(DescriptionButton.id, false)?.autoSize(true);
}
};
NavbarCaption.id = "caption";
// src/components/Navbar.ts
var AVAILABLE_BUTTONS = {};
var AVAILABLE_GROUPS = {};
function registerButton(button, defaultPosition) {
if (!button.id) {
throw new PSVError("Button id is required");
}
AVAILABLE_BUTTONS[button.id] = button;
if (button.groupId) {
(AVAILABLE_GROUPS[button.groupId] = AVAILABLE_GROUPS[button.groupId] || []).push(button);
}
if (defaultPosition) {
const navbar = DEFAULTS.navbar;
switch (defaultPosition) {
case "start":
navbar.unshift(button.id);
break;
case "end":
navbar.push(button.id);
break;
default: {
const [id, pos] = defaultPosition.split(":");
const idx = navbar.indexOf(id);
if (!id || !pos || idx === -1) {
throw new PSVError(`Invalid defaultPosition ${defaultPosition}`);
}
navbar.splice(idx + (pos === "right" ? 1 : 0), 0, button.id);
}
}
}
}
[
ZoomOutButton,
ZoomRangeButton,
ZoomInButton,
DescriptionButton,
NavbarCaption,
DownloadButton,
FullscreenButton,
MoveLeftButton,
MoveRightButton,
MoveUpButton,
MoveDownButton
].forEach((btn) => registerButton(btn));
var Navbar = class extends AbstractComponent {
/**
* @internal
*/
constructor(viewer) {
super(viewer, {
className: `psv-navbar ${CAPTURE_EVENTS_CLASS}`
});
/**
* @internal
*/
this.collapsed = [];
this.state.visible = false;
}
/**
* Shows the navbar
*/
show() {
this.viewer.container.classList.add("psv--has-navbar");
this.container.classList.add("psv-navbar--open");
this.state.visible = true;
}
/**
* Hides the navbar
*/
hide() {
this.viewer.container.classList.remove("psv--has-navbar");
this.container.classList.remove("psv-navbar--open");
this.state.visible = false;
}
/**
* Change the buttons visible on the navbar
*/
setButtons(buttons) {
this.children.slice().forEach((item) => item.destroy());
this.children.length = 0;
if (buttons.indexOf(NavbarCaption.id) !== -1 && buttons.indexOf(DescriptionButton.id) === -1) {
buttons.splice(buttons.indexOf(NavbarCaption.id), 0, DescriptionButton.id);
}
buttons.forEach((button) => {
if (typeof button === "object") {
new CustomButton(this, button);
} else if (AVAILABLE_BUTTONS[button]) {
new AVAILABLE_BUTTONS[button](this);
} else if (AVAILABLE_GROUPS[button]) {
AVAILABLE_GROUPS[button].forEach((buttonCtor) => {
new buttonCtor(this);
});
} else {
logWarn(`Unknown button ${button}`);
}
});
new MenuButton(this);
this.children.forEach((item) => {
if (item instanceof AbstractButton) {
item.checkSupported();
}
});
this.autoSize();
}
/**
* Changes the navbar caption
*/
setCaption(html) {
this.children.some((item) => {
if (item instanceof NavbarCaption) {
item.setCaption(html);
return true;
} else {
return false;
}
});
}
/**
* Returns a button by its identifier
*/
getButton(id, warnNotFound = true) {
const button = this.children.find((item) => {
return item instanceof AbstractButton && item.id === id;
});
if (!button && warnNotFound) {
logWarn(`button "${id}" not found in the navbar`);
}
return button;
}
/**
* Automatically collapses buttons
* @internal
*/
autoSize() {
this.children.forEach((child) => {
if (child instanceof AbstractButton) {
child.autoSize();
}
});
const availableWidth = this.container.offsetWidth;
let totalWidth = 0;
const collapsableButtons = [];
this.children.forEach((item) => {
if (item.isVisible() && item instanceof AbstractButton) {
totalWidth += item.width;
if (item.collapsable) {
collapsableButtons.push(item);
}
}
});
if (totalWidth === 0) {
return;
}
if (availableWidth < totalWidth && collapsableButtons.length > 0) {
collapsableButtons.forEach((item) => item.collapse());
this.collapsed = collapsableButtons;
this.getButton(MenuButton.id).show(false);
} else if (availableWidth >= totalWidth && this.collapsed.length > 0) {
this.collapsed.forEach((item) => item.uncollapse());
this.collapsed = [];
this.getButton(MenuButton.id).hide(false);
}
this.getButton(NavbarCaption.id, false)?.autoSize();
}
};
// src/data/cache.ts
var import_three7 = require("three");
import_three7.Cache.enabled = false;
var Cache = {
enabled: true,
maxItems: 10,
ttl: 10 * 60,
items: {},
purgeInterval: null,
init() {
if (import_three7.Cache.enabled) {
logWarn("ThreeJS cache should be disabled");
import_three7.Cache.enabled = false;
}
if (!this.purgeInterval && this.enabled) {
this.purgeInterval = setInterval(() => this.purge(), 60 * 1e3);
}
},
add(url, key, data) {
if (this.enabled && key) {
this.items[key] = this.items[key] ?? { files: {}, lastAccess: null };
this.items[key].files[url] = data;
this.items[key].lastAccess = Date.now();
}
},
get(url, key) {
if (this.enabled && key && this.items[key]) {
this.items[key].lastAccess = Date.now();
return this.items[key].files[url];
}
},
remove(url, key) {
if (this.enabled && key && this.items[key]) {
delete this.items[key].files[url];
if (Object.keys(this.items[key].files).length === 0) {
delete this.items[key];
}
}
},
purge() {
Object.entries(this.items).sort(([, a], [, b]) => {
return b.lastAccess - a.lastAccess;
}).forEach(([key, { lastAccess }], index) => {
if (index > 0 && (Date.now() - lastAccess >= this.ttl * 1e3 || index >= this.maxItems)) {
delete this.items[key];
}
});
}
};
// src/components/Loader.ts
var Loader = class extends AbstractComponent {
/**
* @internal
*/
constructor(viewer) {
super(viewer, { className: "psv-loader-container" });
this.loader = document.createElement("div");
this.loader.className = "psv-loader";
this.container.appendChild(this.loader);
this.size = this.loader.offsetWidth;
this.canvas = document.createElementNS("http://www.w3.org/2000/svg", "svg");
this.canvas.setAttribute("class", "psv-loader-canvas");
this.canvas.setAttribute("viewBox", `0 0 ${this.size} ${this.size}`);
this.loader.appendChild(this.canvas);
this.textColor = getStyleProperty(this.loader, "color");
this.color = getStyleProperty(this.canvas, "color");
this.border = parseInt(getStyleProperty(this.loader, "--psv-loader-border"), 10);
this.thickness = parseInt(getStyleProperty(this.loader, "--psv-loader-tickness"), 10);
this.viewer.addEventListener(ConfigChangedEvent.type, this);
this.__updateContent();
this.hide();
}
/**
* @internal
*/
destroy() {
this.viewer.removeEventListener(ConfigChangedEvent.type, this);
super.destroy();
}
/**
* @internal
*/
handleEvent(e) {
if (e instanceof ConfigChangedEvent) {
e.containsOptions("loadingImg", "loadingTxt") && this.__updateContent();
}
}
/**
* Sets the loader progression
*/
setProgress(value) {
const angle2 = Math.min(value, 99.999) / 100 * Math.PI * 2;
const halfSize = this.size / 2;
const startX = halfSize;
const startY = this.thickness / 2 + this.border;
const radius = (this.size - this.thickness) / 2 - this.border;
const endX = Math.sin(angle2) * radius + halfSize;
const endY = -Math.cos(angle2) * radius + halfSize;
const largeArc = value > 50 ? "1" : "0";
this.canvas.innerHTML = `
`;
this.viewer.dispatchEvent(new LoadProgressEvent(Math.round(value)));
}
__updateContent() {
const current = this.loader.querySelector(".psv-loader-image, .psv-loader-text");
if (current) {
this.loader.removeChild(current);
}
let inner;
if (this.viewer.config.loadingImg) {
inner = document.createElement("img");
inner.className = "psv-loader-image";
inner.src = this.viewer.config.loadingImg;
} else if (this.viewer.config.loadingTxt !== null) {
inner = document.createElement("div");
inner.className = "psv-loader-text";
inner.innerHTML = this.viewer.config.loadingTxt || this.viewer.config.lang.loading;
}
if (inner) {
const size = Math.round(Math.sqrt(2 * Math.pow(this.size / 2 - this.thickness / 2 - this.border, 2)));
inner.style.maxWidth = size + "px";
inner.style.maxHeight = size + "px";
this.loader.appendChild(inner);
}
}
};
// src/components/Notification.ts
var Notification = class extends AbstractComponent {
/**
* @internal
*/
constructor(viewer) {
super(viewer, {
className: "psv-notification"
});
/**
* @internal
*/
this.state = {
visible: false,
contentId: null,
timeout: null
};
this.content = document.createElement("div");
this.content.className = "psv-notification-content";
this.container.appendChild(this.content);
this.content.addEventListener("click", () => this.hide());
}
/**
* Checks if the notification is visible
*/
isVisible(id) {
return this.state.visible && (!id || !this.state.contentId || this.state.contentId === id);
}
/**
* @throws {@link PSVError} always
* @internal
*/
toggle() {
throw new PSVError("Notification cannot be toggled");
}
/**
* Displays a notification on the viewer
*
* @example
* viewer.showNotification({ content: 'Hello world', timeout: 5000 })
* @example
* viewer.showNotification('Hello world')
*/
show(config) {
if (this.state.timeout) {
clearTimeout(this.state.timeout);
this.state.timeout = null;
}
if (typeof config === "string") {
config = { content: config };
}
this.state.contentId = config.id || null;
this.content.innerHTML = config.content;
this.container.classList.add("psv-notification--visible");
this.state.visible = true;
this.viewer.dispatchEvent(new ShowNotificationEvent(config.id));
if (config.timeout) {
this.state.timeout = setTimeout(() => this.hide(this.state.contentId), config.timeout);
}
}
/**
* Hides the notification
*/
hide(id) {
if (this.isVisible(id)) {
const contentId = this.state.contentId;
this.container.classList.remove("psv-notification--visible");
this.state.visible = false;
this.state.contentId = null;
this.viewer.dispatchEvent(new HideNotificationEvent(contentId));
}
}
};
// src/components/Overlay.ts
var Overlay = class extends AbstractComponent {
/**
* @internal
*/
constructor(viewer) {
super(viewer, {
className: `psv-overlay ${CAPTURE_EVENTS_CLASS}`
});
/**
* @internal
*/
this.state = {
visible: false,
contentId: null,
dissmisable: true
};
this.image = document.createElement("div");
this.image.className = "psv-overlay-image";
this.container.appendChild(this.image);
this.title = document.createElement("div");
this.title.className = "psv-overlay-title";
this.container.appendChild(this.title);
this.text = document.createElement("div");
this.text.className = "psv-overlay-text";
this.container.appendChild(this.text);
this.container.addEventListener("click", this);
this.viewer.addEventListener(KeypressEvent.type, this);
super.hide();
}
/**
* @internal
*/
destroy() {
this.viewer.removeEventListener(KeypressEvent.type, this);
super.destroy();
}
/**
* @internal
*/
handleEvent(e) {
if (e.type === "click") {
if (this.isVisible() && this.state.dissmisable) {
this.hide();
e.stopPropagation();
}
} else if (e instanceof KeypressEvent) {
if (this.isVisible() && this.state.dissmisable && e.key === KEY_CODES.Escape) {
this.hide();
e.preventDefault();
}
}
}
/**
* Checks if the overlay is visible
*/
isVisible(id) {
return this.state.visible && (!id || !this.state.contentId || this.state.contentId === id);
}
/**
* @throws {@link PSVError} always
* @internal
*/
toggle() {
throw new PSVError("Overlay cannot be toggled");
}
/**
* Displays an overlay on the viewer
*/
show(config) {
if (typeof config === "string") {
config = { title: config };
}
this.state.contentId = config.id || null;
this.state.dissmisable = config.dissmisable !== false;
this.image.innerHTML = config.image || "";
this.title.innerHTML = config.title || "";
this.text.innerHTML = config.text || "";
super.show();
this.viewer.dispatchEvent(new ShowOverlayEvent(config.id));
}
/**
* Hides the overlay
*/
hide(id) {
if (this.isVisible(id)) {
const contentId = this.state.contentId;
super.hide();
this.state.contentId = null;
this.viewer.dispatchEvent(new HideOverlayEvent(contentId));
}
}
};
// src/components/Panel.ts
var PANEL_MIN_WIDTH = 200;
var PANEL_CLASS_NO_INTERACTION = "psv-panel-content--no-interaction";
var Panel = class extends AbstractComponent {
/**
* @internal
*/
constructor(viewer) {
super(viewer, {
className: `psv-panel ${CAPTURE_EVENTS_CLASS}`
});
/**
* @internal
*/
this.state = {
visible: false,
contentId: null,
mouseX: 0,
mouseY: 0,
mousedown: false,
clickHandler: null,
keyHandler: null,
width: {}
};
const resizer = document.createElement("div");
resizer.className = "psv-panel-resizer";
this.container.appendChild(resizer);
const closeBtn = document.createElement("div");
closeBtn.className = "psv-panel-close-button";
closeBtn.innerHTML = ICONS.close;
closeBtn.title = viewer.config.lang.close;
this.container.appendChild(closeBtn);
this.content = document.createElement("div");
this.content.className = "psv-panel-content";
this.container.appendChild(this.content);
this.container.addEventListener("wheel", (e) => e.stopPropagation());
closeBtn.addEventListener("click", () => this.hide());
resizer.addEventListener("mousedown", this);
resizer.addEventListener("touchstart", this);
this.viewer.container.addEventListener("mouseup", this);
this.viewer.container.addEventListener("touchend", this);
this.viewer.container.addEventListener("mousemove", this);
this.viewer.container.addEventListener("touchmove", this);
this.viewer.addEventListener(KeypressEvent.type, this);
}
/**
* @internal
*/
destroy() {
this.viewer.removeEventListener(KeypressEvent.type, this);
this.viewer.container.removeEventListener("mousemove", this);
this.viewer.container.removeEventListener("touchmove", this);
this.viewer.container.removeEventListener("mouseup", this);
this.viewer.container.removeEventListener("touchend", this);
super.destroy();
}
/**
* @internal
*/
handleEvent(e) {
switch (e.type) {
case "mousedown":
this.__onMouseDown(e);
break;
case "touchstart":
this.__onTouchStart(e);
break;
case "mousemove":
this.__onMouseMove(e);
break;
case "touchmove":
this.__onTouchMove(e);
break;
case "mouseup":
this.__onMouseUp(e);
break;
case "touchend":
this.__onTouchEnd(e);
break;
case KeypressEvent.type:
this.__onKeyPress(e);
break;
}
}
/**
* Checks if the panel is visible
*/
isVisible(id) {
return this.state.visible && (!id || !this.state.contentId || this.state.contentId === id);
}
/**
* @throws {@link PSVError} always
* @internal
*/
toggle() {
throw new PSVError("Panel cannot be toggled");
}
/**
* Shows the panel
*/
show(config) {
if (typeof config === "string") {
config = { content: config };
}
const wasVisible = this.isVisible(config.id);
this.state.contentId = config.id || null;
this.state.visible = true;
if (this.state.clickHandler) {
this.content.removeEventListener("click", this.state.clickHandler);
this.content.removeEventListener("keydown", this.state.keyHandler);
this.state.clickHandler = null;
this.state.keyHandler = null;
}
if (config.id && this.state.width[config.id]) {
this.container.style.width = this.state.width[config.id];
} else if (config.width) {
this.container.style.width = config.width;
} else {
this.container.style.width = null;
}
this.content.innerHTML = config.content;
this.content.scrollTop = 0;
this.container.classList.add("psv-panel--open");
toggleClass(this.content, "psv-panel-content--no-margin", config.noMargin === true);
if (config.clickHandler) {
this.state.clickHandler = (e) => {
config.clickHandler(getEventTarget(e));
};
this.state.keyHandler = (e) => {
if (e.key === KEY_CODES.Enter) {
config.clickHandler(getEventTarget(e));
}
};
this.content.addEventListener("click", this.state.clickHandler);
this.content.addEventListener("keydown", this.state.keyHandler);
if (!wasVisible) {
setTimeout(() => {
this.content.querySelector("a,button,[tabindex]")?.focus();
}, 300);
}
}
this.viewer.dispatchEvent(new ShowPanelEvent(config.id));
}
/**
* Hides the panel
*/
hide(id) {
if (this.isVisible(id)) {
const contentId = this.state.contentId;
this.state.visible = false;
this.state.contentId = null;
this.content.innerHTML = null;
this.container.classList.remove("psv-panel--open");
if (this.state.clickHandler) {
this.content.removeEventListener("click", this.state.clickHandler);
this.state.clickHandler = null;
}
this.viewer.dispatchEvent(new HidePanelEvent(contentId));
}
}
__onMouseDown(evt) {
evt.stopPropagation();
this.__startResize(evt.clientX, evt.clientY);
}
__onTouchStart(evt) {
evt.stopPropagation();
if (evt.touches.length === 1) {
const touch = evt.touches[0];
this.__startResize(touch.clientX, touch.clientY);
}
}
__onMouseUp(evt) {
if (this.state.mousedown) {
evt.stopPropagation();
this.state.mousedown = false;
this.content.classList.remove(PANEL_CLASS_NO_INTERACTION);
}
}
__onTouchEnd(evt) {
if (this.state.mousedown) {
evt.stopPropagation();
if (evt.touches.length === 0) {
this.state.mousedown = false;
this.content.classList.remove(PANEL_CLASS_NO_INTERACTION);
}
}
}
__onMouseMove(evt) {
if (this.state.mousedown) {
evt.stopPropagation();
this.__resize(evt.clientX, evt.clientY);
}
}
__onTouchMove(evt) {
if (this.state.mousedown) {
const touch = evt.touches[0];
this.__resize(touch.clientX, touch.clientY);
}
}
__onKeyPress(evt) {
if (this.isVisible() && evt.key === KEY_CODES.Escape) {
this.hide();
evt.preventDefault();
}
}
__startResize(clientX, clientY) {
this.state.mouseX = clientX;
this.state.mouseY = clientY;
this.state.mousedown = true;
this.content.classList.add(PANEL_CLASS_NO_INTERACTION);
}
__resize(clientX, clientY) {
const x = clientX;
const y = clientY;
const width = Math.max(PANEL_MIN_WIDTH, this.container.offsetWidth - (x - this.state.mouseX)) + "px";
if (this.state.contentId) {
this.state.width[this.state.contentId] = width;
}
this.container.style.width = width;
this.state.mouseX = x;
this.state.mouseY = y;
}
};
// src/components/Tooltip.ts
var Tooltip = class extends AbstractComponent {
/**
* @internal
*/
constructor(viewer, config) {
super(viewer, {
className: "psv-tooltip"
});
/**
* @internal
*/
this.state = {
visible: true,
arrow: 0,
border: 0,
state: 0 /* NONE */,
width: 0,
height: 0,
pos: "",
config: null,
data: null,
hideTimeout: null
};
this.content = document.createElement("div");
this.content.className = "psv-tooltip-content";
this.container.appendChild(this.content);
this.arrow = document.createElement("div");
this.arrow.className = "psv-tooltip-arrow";
this.container.appendChild(this.arrow);
this.container.addEventListener("transitionend", this);
this.container.addEventListener("touchdown", (e) => e.stopPropagation());
this.container.addEventListener("mousedown", (e) => e.stopPropagation());
this.container.style.top = "-1000px";
this.container.style.left = "-1000px";
this.show(config);
}
/**
* @internal
*/
handleEvent(e) {
if (e.type === "transitionend") {
this.__onTransitionEnd(e);
}
}
/**
* @internal
*/
destroy() {
clearTimeout(this.state.hideTimeout);
delete this.state.data;
super.destroy();
}
/**
* @throws {@link PSVError} always
* @internal
*/
toggle() {
throw new PSVError("Tooltip cannot be toggled");
}
/**
* Displays the tooltip on the viewer
* @internal
*/
show(config) {
if (this.state.state !== 0 /* NONE */) {
throw new PSVError("Initialized tooltip cannot be re-initialized");
}
if (config.className) {
addClasses(this.container, config.className);
}
if (config.style) {
Object.assign(this.container.style, config.style);
}
this.state.state = 3 /* READY */;
this.update(config.content, config);
this.state.data = config.data;
this.state.state = 1 /* SHOWING */;
this.viewer.dispatchEvent(new ShowTooltipEvent(this, this.state.data));
this.__waitImages();
}
/**
* Updates the content of the tooltip, optionally with a new position
* @throws {@link PSVError} if the configuration is invalid
*/
update(content, config) {
this.content.innerHTML = content;
const rect = this.container.getBoundingClientRect();
this.state.width = rect.right - rect.left;
this.state.height = rect.bottom - rect.top;
this.state.arrow = parseInt(getStyleProperty(this.arrow, "border-top-width"), 10);
this.state.border = parseInt(getStyleProperty(this.container, "border-top-left-radius"), 10);
this.move(config ?? this.state.config);
this.__waitImages();
}
/**
* Moves the tooltip to a new position
* @throws {@link PSVError} if the configuration is invalid
*/
move(config) {
if (this.state.state !== 1 /* SHOWING */ && this.state.state !== 3 /* READY */) {
throw new PSVError("Uninitialized tooltip cannot be moved");
}
config.box = config.box ?? this.state.config?.box ?? { width: 0, height: 0 };
this.state.config = config;
const t = this.container;
const a = this.arrow;
const style = {
posClass: cleanCssPosition(config.position, { allowCenter: false, cssOrder: false }) || ["top", "center"],
width: this.state.width,
height: this.state.height,
top: 0,
left: 0,
arrowTop: 0,
arrowLeft: 0
};
this.__computeTooltipPosition(style, config);
let swapY = null;
let swapX = null;
if (style.top < 0) {
swapY = "bottom";
} else if (style.top + style.height > this.viewer.state.size.height) {
swapY = "top";
}
if (style.left < 0) {
swapX = "right";
} else if (style.left + style.width > this.viewer.state.size.width) {
swapX = "left";
}
if (swapX || swapY) {
const ordered = cssPositionIsOrdered(style.posClass);
if (swapY) {
style.posClass[ordered ? 0 : 1] = swapY;
}
if (swapX) {
style.posClass[ordered ? 1 : 0] = swapX;
}
this.__computeTooltipPosition(style, config);
}
t.style.top = style.top + "px";
t.style.left = style.left + "px";
a.style.top = style.arrowTop + "px";
a.style.left = style.arrowLeft + "px";
const newPos = style.posClass.join("-");
if (newPos !== this.state.pos) {
t.classList.remove(`psv-tooltip--${this.state.pos}`);
this.state.pos = newPos;
t.classList.add(`psv-tooltip--${this.state.pos}`);
}
}
/**
* Hides the tooltip
*/
hide() {
this.container.classList.remove("psv-tooltip--visible");
this.state.state = 2 /* HIDING */;
this.viewer.dispatchEvent(new HideTooltipEvent(this.state.data));
const duration = parseFloat(getStyleProperty(this.container, "transition-duration"));
this.state.hideTimeout = setTimeout(() => {
this.destroy();
}, duration * 2);
}
/**
* Finalize transition
*/
__onTransitionEnd(e) {
if (e.propertyName === "transform") {
switch (this.state.state) {
case 1 /* SHOWING */:
this.container.classList.add("psv-tooltip--visible");
this.state.state = 3 /* READY */;
break;
case 2 /* HIDING */:
this.state.state = 0 /* NONE */;
this.destroy();
break;
default:
}
}
}
/**
* Computes the position of the tooltip and its arrow
*/
__computeTooltipPosition(style, config) {
const arrow = this.state.arrow;
const top = config.top;
const height = style.height;
const left = config.left;
const width = style.width;
const offsetSide = arrow + this.state.border;
const offsetX = config.box.width / 2 + arrow * 2;
const offsetY = config.box.height / 2 + arrow * 2;
switch (style.posClass.join("-")) {
case "top-left":
style.top = top - offsetY - height;
style.left = left + offsetSide - width;
style.arrowTop = height;
style.arrowLeft = width - offsetSide - arrow;
break;
case "top-center":
style.top = top - offsetY - height;
style.left = left - width / 2;
style.arrowTop = height;
style.arrowLeft = width / 2 - arrow;
break;
case "top-right":
style.top = top - offsetY - height;
style.left = left - offsetSide;
style.arrowTop = height;
style.arrowLeft = arrow;
break;
case "bottom-left":
style.top = top + offsetY;
style.left = left + offsetSide - width;
style.arrowTop = -arrow * 2;
style.arrowLeft = width - offsetSide - arrow;
break;
case "bottom-center":
style.top = top + offsetY;
style.left = left - width / 2;
style.arrowTop = -arrow * 2;
style.arrowLeft = width / 2 - arrow;
break;
case "bottom-right":
style.top = top + offsetY;
style.left = left - offsetSide;
style.arrowTop = -arrow * 2;
style.arrowLeft = arrow;
break;
case "left-top":
style.top = top + offsetSide - height;
style.left = left - offsetX - width;
style.arrowTop = height - offsetSide - arrow;
style.arrowLeft = width;
break;
case "center-left":
style.top = top - height / 2;
style.left = left - offsetX - width;
style.arrowTop = height / 2 - arrow;
style.arrowLeft = width;
break;
case "left-bottom":
style.top = top - offsetSide;
style.left = left - offsetX - width;
style.arrowTop = arrow;
style.arrowLeft = width;
break;
case "right-top":
style.top = top + offsetSide - height;
style.left = left + offsetX;
style.arrowTop = height - offsetSide - arrow;
style.arrowLeft = -arrow * 2;
break;
case "center-right":
style.top = top - height / 2;
style.left = left + offsetX;
style.arrowTop = height / 2 - arrow;
style.arrowLeft = -arrow * 2;
break;
case "right-bottom":
style.top = top - offsetSide;
style.left = left + offsetX;
style.arrowTop = arrow;
style.arrowLeft = -arrow * 2;
break;
}
}
/**
* If the tooltip contains images, recompute its size once they are loaded
*/
__waitImages() {
const images = this.content.querySelectorAll("img");
if (images.length > 0) {
const promises = [];
images.forEach((image) => {
if (!image.complete) {
promises.push(
new Promise((resolve) => {
image.onload = resolve;
image.onerror = resolve;
})
);
}
});
if (promises.length) {
Promise.all(promises).then(() => {
if (this.state.state === 1 /* SHOWING */ || this.state.state === 3 /* READY */) {
const rect = this.container.getBoundingClientRect();
this.state.width = rect.right - rect.left;
this.state.height = rect.bottom - rect.top;
this.move(this.state.config);
}
});
}
}
}
};
// src/icons/error.svg
var error_default = '\n';
// src/services/DataHelper.ts
var import_three8 = require("three");
// src/services/AbstractService.ts
var AbstractService = class {
/**
* @internal
*/
constructor(viewer) {
this.viewer = viewer;
this.config = viewer.config;
this.state = viewer.state;
}
/**
* Destroys the service
* @internal
*/
// eslint-disable-next-line @typescript-eslint/no-empty-function
destroy() {
}
};
// src/services/DataHelper.ts
var vector3 = new import_three8.Vector3();
var EULER_ZERO = new import_three8.Euler(0, 0, 0, "ZXY");
var DataHelper = class extends AbstractService {
/**
* @internal
*/
constructor(viewer) {
super(viewer);
}
/**
* Converts vertical FOV to zoom level
*/
fovToZoomLevel(fov) {
const temp = Math.round((fov - this.config.minFov) / (this.config.maxFov - this.config.minFov) * 100);
return import_three8.MathUtils.clamp(temp - 2 * (temp - 50), 0, 100);
}
/**
* Converts zoom level to vertical FOV
*/
zoomLevelToFov(level) {
return this.config.maxFov + level / 100 * (this.config.minFov - this.config.maxFov);
}
/**
* Converts vertical FOV to horizontal FOV
*/
vFovToHFov(vFov) {
return import_three8.MathUtils.radToDeg(2 * Math.atan(Math.tan(import_three8.MathUtils.degToRad(vFov) / 2) * this.state.aspect));
}
/**
* Converts horizontal FOV to vertical FOV
*/
hFovToVFov(hFov) {
return import_three8.MathUtils.radToDeg(2 * Math.atan(Math.tan(import_three8.MathUtils.degToRad(hFov) / 2) / this.state.aspect));
}
/**
* @internal
*/
getAnimationProperties(speed, targetPosition, targetZoom) {
const positionProvided = !isNil(targetPosition);
const zoomProvided = !isNil(targetZoom);
const properties = {};
let duration = null;
if (positionProvided) {
const currentPosition = this.viewer.getPosition();
const dYaw = getShortestArc(currentPosition.yaw, targetPosition.yaw);
properties.yaw = { start: currentPosition.yaw, end: currentPosition.yaw + dYaw };
properties.pitch = { start: currentPosition.pitch, end: targetPosition.pitch };
duration = speedToDuration(speed, getAngle(currentPosition, targetPosition));
}
if (zoomProvided) {
const currentZoom = this.viewer.getZoomLevel();
const dZoom = Math.abs(targetZoom - currentZoom);
properties.zoom = { start: currentZoom, end: targetZoom };
if (duration === null) {
duration = speedToDuration(speed, Math.PI / 4 * dZoom / 100);
}
}
if (duration === null) {
if (typeof speed === "number") {
duration = speed;
} else {
duration = ANIMATION_MIN_DURATION;
}
} else {
duration = Math.max(ANIMATION_MIN_DURATION, duration);
}
return { duration, properties };
}
/**
* Converts pixel texture coordinates to spherical radians coordinates
* @throws {@link PSVError} when the current adapter does not support texture coordinates
*/
textureCoordsToSphericalCoords(point) {
if (!this.state.textureData?.panoData) {
throw new PSVError("Current adapter does not support texture coordinates or no texture has been loaded");
}
const result = this.viewer.adapter.textureCoordsToSphericalCoords(point, this.state.textureData.panoData);
if (!EULER_ZERO.equals(this.viewer.renderer.panoramaPose) || !EULER_ZERO.equals(this.viewer.renderer.sphereCorrection)) {
this.sphericalCoordsToVector3(result, vector3);
vector3.applyEuler(this.viewer.renderer.panoramaPose);
vector3.applyEuler(this.viewer.renderer.sphereCorrection);
return this.vector3ToSphericalCoords(vector3);
} else {
return result;
}
}
/**
* Converts spherical radians coordinates to pixel texture coordinates
* @throws {@link PSVError} when the current adapter does not support texture coordinates
*/
sphericalCoordsToTextureCoords(position) {
if (!this.state.textureData?.panoData) {
throw new PSVError("Current adapter does not support texture coordinates or no texture has been loaded");
}
if (!EULER_ZERO.equals(this.viewer.renderer.panoramaPose) || !EULER_ZERO.equals(this.viewer.renderer.sphereCorrection)) {
this.sphericalCoordsToVector3(position, vector3);
applyEulerInverse(vector3, this.viewer.renderer.sphereCorrection);
applyEulerInverse(vector3, this.viewer.renderer.panoramaPose);
position = this.vector3ToSphericalCoords(vector3);
}
return this.viewer.adapter.sphericalCoordsToTextureCoords(position, this.state.textureData.panoData);
}
/**
* Converts spherical radians coordinates to a Vector3
*/
sphericalCoordsToVector3(position, vector, distance2 = SPHERE_RADIUS) {
if (!vector) {
vector = new import_three8.Vector3();
}
vector.x = distance2 * -Math.cos(position.pitch) * Math.sin(position.yaw);
vector.y = distance2 * Math.sin(position.pitch);
vector.z = distance2 * Math.cos(position.pitch) * Math.cos(position.yaw);
return vector;
}
/**
* Converts a Vector3 to spherical radians coordinates
*/
vector3ToSphericalCoords(vector) {
const phi = Math.acos(vector.y / Math.sqrt(vector.x * vector.x + vector.y * vector.y + vector.z * vector.z));
const theta = Math.atan2(vector.x, vector.z);
return {
yaw: theta < 0 ? -theta : Math.PI * 2 - theta,
pitch: Math.PI / 2 - phi
};
}
/**
* Converts position on the viewer to a THREE.Vector3
*/
viewerCoordsToVector3(viewerPoint) {
const sphereIntersect = this.viewer.renderer.getIntersections(viewerPoint).filter((i) => i.object.userData[VIEWER_DATA]);
if (sphereIntersect.length) {
return sphereIntersect[0].point;
} else {
return null;
}
}
/**
* Converts position on the viewer to spherical radians coordinates
*/
viewerCoordsToSphericalCoords(viewerPoint) {
const vector = this.viewerCoordsToVector3(viewerPoint);
return vector ? this.vector3ToSphericalCoords(vector) : null;
}
/**
* Converts a Vector3 to position on the viewer
*/
vector3ToViewerCoords(vector) {
const vectorClone = vector.clone();
vectorClone.project(this.viewer.renderer.camera);
return {
x: Math.round((vectorClone.x + 1) / 2 * this.state.size.width),
y: Math.round((1 - vectorClone.y) / 2 * this.state.size.height)
};
}
/**
* Converts spherical radians coordinates to position on the viewer
*/
sphericalCoordsToViewerCoords(position) {
this.sphericalCoordsToVector3(position, vector3);
return this.vector3ToViewerCoords(vector3);
}
/**
* @internal
*/
isPointVisible(point) {
let vector;
let viewerPoint;
if (point instanceof import_three8.Vector3) {
vector = point;
viewerPoint = this.vector3ToViewerCoords(point);
} else if (isExtendedPosition(point)) {
vector = this.sphericalCoordsToVector3(point, vector3);
viewerPoint = this.vector3ToViewerCoords(vector);
} else {
return false;
}
return vector.dot(this.viewer.state.direction) > 0 && viewerPoint.x >= 0 && viewerPoint.x <= this.viewer.state.size.width && viewerPoint.y >= 0 && viewerPoint.y <= this.viewer.state.size.height;
}
/**
* Converts pixel position to angles if present and ensure boundaries
*/
cleanPosition(position) {
if ("yaw" in position || "pitch" in position) {
if (!("yaw" in position) || !("pitch" in position)) {
throw new PSVError(`Position is missing 'yaw' or 'pitch'`);
}
return {
yaw: parseAngle(position.yaw),
pitch: parseAngle(position.pitch, true)
};
} else {
return this.textureCoordsToSphericalCoords(position);
}
}
/**
* Ensure a SphereCorrection object is valid
*/
cleanSphereCorrection(sphereCorrection) {
return {
pan: parseAngle(sphereCorrection?.pan || 0),
tilt: parseAngle(sphereCorrection?.tilt || 0, true),
roll: parseAngle(sphereCorrection?.roll || 0, true, false)
};
}
/**
* Parse the pose angles of the pano data
*/
cleanPanoramaPose(panoData) {
return {
pan: import_three8.MathUtils.degToRad(panoData?.poseHeading || 0),
tilt: import_three8.MathUtils.degToRad(panoData?.posePitch || 0),
roll: import_three8.MathUtils.degToRad(panoData?.poseRoll || 0)
};
}
/**
* Update the panorama options if the panorama files contains "InitialView" metadata
*/
cleanPanoramaOptions(options, panoData) {
if (!panoData?.isEquirectangular) {
return options;
}
if (isNil(options.zoom) && !isNil(panoData.initialFov)) {
options = {
...options,
zoom: this.fovToZoomLevel(this.hFovToVFov(panoData.initialFov))
};
}
if (isNil(options.position) && !isNil(panoData.initialHeading) && !isNil(panoData.initialPitch)) {
options = {
...options,
position: {
yaw: parseAngle(panoData.initialHeading),
pitch: parseAngle(panoData.initialPitch, true)
}
};
}
return options;
}
};
// src/services/EventsHandler.ts
var import_three9 = require("three");
// src/icons/gesture.svg
var gesture_default = '\n';
// src/icons/mousewheel.svg
var mousewheel_default = '\n';
// src/services/EventsHandler.ts
var _Step = class _Step {
constructor() {
this.$ = _Step.IDLE;
}
is(...steps) {
return steps.some((step) => this.$ & step);
}
set(step) {
this.$ = step;
}
add(step) {
this.$ |= step;
}
remove(step) {
this.$ &= ~step;
}
};
_Step.IDLE = 0;
_Step.CLICK = 1;
_Step.MOVING = 2;
var Step = _Step;
var EventsHandler = class extends AbstractService {
constructor(viewer) {
super(viewer);
this.data = {
/** start x position of the click/touch */
startMouseX: 0,
/** start y position of the click/touch */
startMouseY: 0,
/** current x position of the cursor */
mouseX: 0,
/** current y position of the cursor */
mouseY: 0,
/** current distance between fingers */
pinchDist: 0,
/** accumulator for smooth movement */
moveDelta: { yaw: 0, pitch: 0, zoom: 0 },
accumulatorFactor: 0,
/** when the Ctrl key is pressed */
ctrlKeyDown: false,
/** temporary storage of click data between two clicks */
dblclickData: null,
dblclickTimeout: null,
longtouchTimeout: null,
twofingersTimeout: null,
ctrlZoomTimeout: null
};
this.step = new Step();
this.keyHandler = new PressHandler();
this.resizeObserver = new ResizeObserver(throttle(() => this.viewer.autoSize(), 50));
this.moveThreshold = MOVE_THRESHOLD * SYSTEM.pixelRatio;
}
/**
* @internal
*/
init() {
window.addEventListener("keydown", this, { passive: false });
window.addEventListener("keyup", this);
this.viewer.container.addEventListener("mousedown", this);
window.addEventListener("mousemove", this, { passive: false });
window.addEventListener("mouseup", this);
this.viewer.container.addEventListener("touchstart", this, { passive: false });
window.addEventListener("touchmove", this, { passive: false });
window.addEventListener("touchend", this, { passive: false });
this.viewer.container.addEventListener("wheel", this, { passive: false });
document.addEventListener("fullscreenchange", this);
this.resizeObserver.observe(this.viewer.container);
this.viewer.addEventListener(BeforeRenderEvent.type, this);
this.viewer.addEventListener(StopAllEvent.type, this);
}
destroy() {
window.removeEventListener("keydown", this);
window.removeEventListener("keyup", this);
this.viewer.container.removeEventListener("mousedown", this);
window.removeEventListener("mousemove", this);
window.removeEventListener("mouseup", this);
this.viewer.container.removeEventListener("touchstart", this);
window.removeEventListener("touchmove", this);
window.removeEventListener("touchend", this);
this.viewer.container.removeEventListener("wheel", this);
document.removeEventListener("fullscreenchange", this);
this.resizeObserver.disconnect();
this.viewer.removeEventListener(BeforeRenderEvent.type, this);
this.viewer.removeEventListener(StopAllEvent.type, this);
clearTimeout(this.data.dblclickTimeout);
clearTimeout(this.data.longtouchTimeout);
clearTimeout(this.data.twofingersTimeout);
clearTimeout(this.data.ctrlZoomTimeout);
super.destroy();
}
/**
* @internal
*/
handleEvent(evt) {
switch (evt.type) {
case "keydown":
this.__onKeyDown(evt);
break;
case "keyup":
this.__onKeyUp();
break;
case "mousemove":
this.__onMouseMove(evt);
break;
case "mouseup":
this.__onMouseUp(evt);
break;
case "touchmove":
this.__onTouchMove(evt);
break;
case "touchend":
this.__onTouchEnd(evt);
break;
case "fullscreenchange":
this.__onFullscreenChange();
break;
case BeforeRenderEvent.type:
this.__applyMoveDelta();
break;
case StopAllEvent.type:
this.__clearMoveDelta();
break;
}
if (!getMatchingTarget(evt, "." + CAPTURE_EVENTS_CLASS)) {
switch (evt.type) {
case "mousedown":
this.__onMouseDown(evt);
break;
case "touchstart":
this.__onTouchStart(evt);
break;
case "wheel":
this.__onMouseWheel(evt);
break;
}
}
}
/**
* Handles keyboard events
*/
__onKeyDown(e) {
if (this.config.mousewheelCtrlKey) {
this.data.ctrlKeyDown = e.key === KEY_CODES.Control;
if (this.data.ctrlKeyDown) {
clearTimeout(this.data.ctrlZoomTimeout);
this.viewer.overlay.hide(IDS.CTRL_ZOOM);
}
}
if (!this.viewer.dispatchEvent(new KeypressEvent(e.key, e))) {
return;
}
if (!this.state.keyboardEnabled) {
return;
}
const action = this.config.keyboardActions?.[e.key];
if (typeof action === "function") {
action(this.viewer, e);
e.preventDefault();
return;
}
if (e.ctrlKey || e.altKey || e.shiftKey || e.metaKey) {
return;
}
if (action && !this.keyHandler.pending) {
if (action !== "ZOOM_IN" /* ZOOM_IN */ && action !== "ZOOM_OUT" /* ZOOM_OUT */) {
this.viewer.stopAll();
}
switch (action) {
case "ROTATE_UP" /* ROTATE_UP */:
this.viewer.dynamics.position.roll({ pitch: false });
break;
case "ROTATE_DOWN" /* ROTATE_DOWN */:
this.viewer.dynamics.position.roll({ pitch: true });
break;
case "ROTATE_RIGHT" /* ROTATE_RIGHT */:
this.viewer.dynamics.position.roll({ yaw: false });
break;
case "ROTATE_LEFT" /* ROTATE_LEFT */:
this.viewer.dynamics.position.roll({ yaw: true });
break;
case "ZOOM_IN" /* ZOOM_IN */:
this.viewer.dynamics.zoom.roll(false);
break;
case "ZOOM_OUT" /* ZOOM_OUT */:
this.viewer.dynamics.zoom.roll(true);
break;
}
this.keyHandler.down(action);
e.preventDefault();
}
}
/**
* Handles keyboard events
*/
__onKeyUp() {
this.data.ctrlKeyDown = false;
if (!this.state.keyboardEnabled) {
return;
}
this.keyHandler.up((action) => {
if (action === "ZOOM_IN" /* ZOOM_IN */ || action === "ZOOM_OUT" /* ZOOM_OUT */) {
this.viewer.dynamics.zoom.stop();
} else {
this.viewer.dynamics.position.stop();
this.viewer.resetIdleTimer();
}
});
}
/**
* Handles mouse down events
*/
__onMouseDown(evt) {
this.step.add(Step.CLICK);
this.data.startMouseX = evt.clientX;
this.data.startMouseY = evt.clientY;
}
/**
*Handles mouse up events
*/
__onMouseUp(evt) {
if (this.step.is(Step.CLICK, Step.MOVING)) {
this.__stopMove(evt.clientX, evt.clientY, evt, evt.button === 2);
}
}
/**
* Handles mouse move events
*/
__onMouseMove(evt) {
if (this.config.mousemove && this.step.is(Step.CLICK, Step.MOVING)) {
evt.preventDefault();
this.__doMove(evt.clientX, evt.clientY);
}
this.__handleObjectsEvents(evt);
}
/**
* Handles touch events
*/
__onTouchStart(evt) {
if (evt.touches.length === 1) {
this.step.add(Step.CLICK);
this.data.startMouseX = evt.touches[0].clientX;
this.data.startMouseY = evt.touches[0].clientY;
if (!this.data.longtouchTimeout) {
this.data.longtouchTimeout = setTimeout(() => {
const touch = evt.touches[0];
this.__stopMove(touch.clientX, touch.clientY, evt, true);
this.data.longtouchTimeout = null;
}, LONGTOUCH_DELAY);
}
} else if (evt.touches.length === 2) {
this.step.set(Step.IDLE);
this.__cancelLongTouch();
if (this.config.mousemove) {
this.__cancelTwoFingersOverlay();
this.__startMoveZoom(evt);
evt.preventDefault();
}
}
}
/**
* Handles touch events
*/
__onTouchEnd(evt) {
this.__cancelLongTouch();
if (this.step.is(Step.CLICK, Step.MOVING)) {
evt.preventDefault();
this.__cancelTwoFingersOverlay();
if (evt.touches.length === 1) {
this.__stopMove(this.data.mouseX, this.data.mouseY);
} else if (evt.touches.length === 0) {
const touch = evt.changedTouches[0];
this.__stopMove(touch.clientX, touch.clientY, evt);
}
}
}
/**
* Handles touch move events
*/
__onTouchMove(evt) {
this.__cancelLongTouch();
if (!this.config.mousemove) {
return;
}
if (evt.touches.length === 1) {
if (this.config.touchmoveTwoFingers) {
if (this.step.is(Step.CLICK) && !this.data.twofingersTimeout) {
this.data.twofingersTimeout = setTimeout(() => {
this.viewer.overlay.show({
id: IDS.TWO_FINGERS,
image: gesture_default,
title: this.config.lang.twoFingers
});
}, TWOFINGERSOVERLAY_DELAY);
}
} else if (this.step.is(Step.CLICK, Step.MOVING)) {
evt.preventDefault();
const touch = evt.touches[0];
this.__doMove(touch.clientX, touch.clientY);
}
} else {
this.__doMoveZoom(evt);
this.__cancelTwoFingersOverlay();
}
}
/**
* Cancel the long touch timer if any
*/
__cancelLongTouch() {
if (this.data.longtouchTimeout) {
clearTimeout(this.data.longtouchTimeout);
this.data.longtouchTimeout = null;
}
}
/**
* Cancel the two fingers overlay timer if any
*/
__cancelTwoFingersOverlay() {
if (this.config.touchmoveTwoFingers) {
if (this.data.twofingersTimeout) {
clearTimeout(this.data.twofingersTimeout);
this.data.twofingersTimeout = null;
}
this.viewer.overlay.hide(IDS.TWO_FINGERS);
}
}
/**
* Handles mouse wheel events
*/
__onMouseWheel(evt) {
if (!this.config.mousewheel || !evt.deltaY) {
return;
}
if (this.config.mousewheelCtrlKey && !this.data.ctrlKeyDown) {
this.viewer.overlay.show({
id: IDS.CTRL_ZOOM,
image: mousewheel_default,
title: this.config.lang.ctrlZoom
});
clearTimeout(this.data.ctrlZoomTimeout);
this.data.ctrlZoomTimeout = setTimeout(() => this.viewer.overlay.hide(IDS.CTRL_ZOOM), CTRLZOOM_TIMEOUT);
return;
}
evt.preventDefault();
evt.stopPropagation();
const delta = evt.deltaY / Math.abs(evt.deltaY) * 5 * this.config.zoomSpeed;
if (delta !== 0) {
this.viewer.dynamics.zoom.step(-delta, 5);
}
}
/**
* Handles fullscreen events
*/
__onFullscreenChange() {
const fullscreen = this.viewer.isFullscreenEnabled();
if (this.config.keyboard === "fullscreen") {
if (fullscreen) {
this.viewer.startKeyboardControl();
} else {
this.viewer.stopKeyboardControl();
}
}
this.viewer.dispatchEvent(new FullscreenEvent(fullscreen));
}
/**
* Resets all state variables
*/
__resetMove() {
this.step.set(Step.IDLE);
this.data.mouseX = 0;
this.data.mouseY = 0;
this.data.startMouseX = 0;
this.data.startMouseY = 0;
}
/**
* Initializes the combines move and zoom
*/
__startMoveZoom(evt) {
this.viewer.stopAll();
this.__resetMove();
const touchData = getTouchData(evt);
this.step.set(Step.MOVING);
this.data.accumulatorFactor = this.config.moveInertia;
({
distance: this.data.pinchDist,
center: { x: this.data.mouseX, y: this.data.mouseY }
} = touchData);
}
/**
* Stops the movement
* @description If the move threshold was not reached a click event is triggered
*/
__stopMove(clientX, clientY, event, rightclick = false) {
if (this.step.is(Step.CLICK) && !this.__moveThresholdReached(clientX, clientY)) {
this.__doClick(clientX, clientY, event, rightclick);
}
if (this.config.moveInertia) {
this.data.accumulatorFactor = Math.pow(this.config.moveInertia, 0.5);
}
this.__resetMove();
this.viewer.resetIdleTimer();
}
/**
* Triggers an event with all coordinates when a simple click is performed
*/
__doClick(clientX, clientY, event, rightclick = false) {
const boundingRect = this.viewer.container.getBoundingClientRect();
const viewerX = clientX - boundingRect.left;
const viewerY = clientY - boundingRect.top;
const intersections = this.viewer.renderer.getIntersections({ x: viewerX, y: viewerY });
const sphereIntersection = intersections.find((i) => i.object.userData[VIEWER_DATA]);
if (sphereIntersection) {
const sphericalCoords = this.viewer.dataHelper.vector3ToSphericalCoords(sphereIntersection.point);
const data = {
rightclick,
originalEvent: event,
target: getEventTarget(event),
clientX,
clientY,
viewerX,
viewerY,
yaw: sphericalCoords.yaw,
pitch: sphericalCoords.pitch,
objects: intersections.map((i) => i.object).filter((o) => !o.userData[VIEWER_DATA])
};
try {
const textureCoords = this.viewer.dataHelper.sphericalCoordsToTextureCoords(data);
Object.assign(data, textureCoords);
} catch (e) {
}
if (!this.data.dblclickTimeout) {
this.viewer.dispatchEvent(new ClickEvent(data));
this.data.dblclickData = clone(data);
this.data.dblclickTimeout = setTimeout(() => {
this.data.dblclickTimeout = null;
this.data.dblclickData = null;
}, DBLCLICK_DELAY);
} else {
if (Math.abs(this.data.dblclickData.clientX - data.clientX) < this.moveThreshold && Math.abs(this.data.dblclickData.clientY - data.clientY) < this.moveThreshold) {
this.viewer.dispatchEvent(new DoubleClickEvent(this.data.dblclickData));
}
clearTimeout(this.data.dblclickTimeout);
this.data.dblclickTimeout = null;
this.data.dblclickData = null;
}
}
}
/**
* Trigger events for observed THREE objects
*/
__handleObjectsEvents(evt) {
if (!isEmpty(this.state.objectsObservers) && evt.composedPath().includes(this.viewer.container)) {
const viewerPos = getPosition(this.viewer.container);
const viewerPoint = {
x: evt.clientX - viewerPos.x,
y: evt.clientY - viewerPos.y
};
const intersections = this.viewer.renderer.getIntersections(viewerPoint);
const emit = (object, key, evtCtor) => {
this.viewer.dispatchEvent(new evtCtor(evt, object, viewerPoint, key));
};
for (const [key, object] of Object.entries(this.state.objectsObservers)) {
const intersection = intersections.find((i) => i.object.userData[key]);
if (intersection) {
if (object && intersection.object !== object) {
emit(object, key, ObjectLeaveEvent);
this.state.objectsObservers[key] = null;
}
if (!object) {
this.state.objectsObservers[key] = intersection.object;
emit(intersection.object, key, ObjectEnterEvent);
} else {
emit(intersection.object, key, ObjectHoverEvent);
}
} else if (object) {
emit(object, key, ObjectLeaveEvent);
this.state.objectsObservers[key] = null;
}
}
}
}
/**
* Starts moving when crossing moveThreshold and performs movement
*/
__doMove(clientX, clientY) {
if (this.step.is(Step.CLICK) && this.__moveThresholdReached(clientX, clientY)) {
this.viewer.stopAll();
this.__resetMove();
this.step.set(Step.MOVING);
this.data.mouseX = clientX;
this.data.mouseY = clientY;
this.data.accumulatorFactor = this.config.moveInertia;
} else if (this.step.is(Step.MOVING)) {
const x = (clientX - this.data.mouseX) * Math.cos(this.state.roll) - (clientY - this.data.mouseY) * Math.sin(this.state.roll);
const y = (clientY - this.data.mouseY) * Math.cos(this.state.roll) + (clientX - this.data.mouseX) * Math.sin(this.state.roll);
const rotation = {
yaw: this.config.moveSpeed * (x / this.state.size.width) * import_three9.MathUtils.degToRad(this.state.hFov),
pitch: this.config.moveSpeed * (y / this.state.size.height) * import_three9.MathUtils.degToRad(this.state.vFov)
};
this.data.moveDelta.yaw += rotation.yaw;
this.data.moveDelta.pitch += rotation.pitch;
this.data.mouseX = clientX;
this.data.mouseY = clientY;
}
}
/**
* Checks if the cursor was move beyond the move threshold
*/
__moveThresholdReached(clientX, clientY) {
return Math.abs(clientX - this.data.startMouseX) >= this.moveThreshold || Math.abs(clientY - this.data.startMouseY) >= this.moveThreshold;
}
/**
* Perfoms combined move and zoom
*/
__doMoveZoom(evt) {
if (this.step.is(Step.MOVING)) {
evt.preventDefault();
const touchData = getTouchData(evt);
this.__doMove(touchData.center.x, touchData.center.y);
this.data.moveDelta.zoom += this.config.zoomSpeed * ((touchData.distance - this.data.pinchDist) / SYSTEM.pixelRatio);
this.data.pinchDist = touchData.distance;
}
}
__applyMoveDelta() {
const EPS = 1e-3;
if (Math.abs(this.data.moveDelta.yaw) > 0 || Math.abs(this.data.moveDelta.pitch) > 0) {
const currentPosition = this.viewer.getPosition();
this.viewer.rotate({
yaw: currentPosition.yaw - this.data.moveDelta.yaw * (1 - this.config.moveInertia),
pitch: currentPosition.pitch + this.data.moveDelta.pitch * (1 - this.config.moveInertia)
});
this.data.moveDelta.yaw *= this.data.accumulatorFactor;
this.data.moveDelta.pitch *= this.data.accumulatorFactor;
if (Math.abs(this.data.moveDelta.yaw) <= EPS) {
this.data.moveDelta.yaw = 0;
}
if (Math.abs(this.data.moveDelta.pitch) <= EPS) {
this.data.moveDelta.pitch = 0;
}
}
if (Math.abs(this.data.moveDelta.zoom) > 0) {
const currentZoom = this.viewer.getZoomLevel();
this.viewer.zoom(currentZoom + this.data.moveDelta.zoom * (1 - this.config.moveInertia));
this.data.moveDelta.zoom *= this.config.moveInertia;
if (Math.abs(this.data.moveDelta.zoom) <= EPS) {
this.data.moveDelta.zoom = 0;
}
}
}
__clearMoveDelta() {
this.data.moveDelta.yaw = 0;
this.data.moveDelta.pitch = 0;
this.data.moveDelta.zoom = 0;
}
};
// src/services/Renderer.ts
var import_three10 = require("three");
import_three10.ColorManagement.enabled = false;
var vector2 = new import_three10.Vector2();
var matrix4 = new import_three10.Matrix4();
var box3 = new import_three10.Box3();
var Renderer = class extends AbstractService {
/**
* @internal
*/
constructor(viewer) {
super(viewer);
this.frustumNeedsUpdate = true;
this.renderer = new import_three10.WebGLRenderer(this.config.rendererParameters);
this.renderer.setPixelRatio(SYSTEM.pixelRatio);
this.renderer.outputColorSpace = import_three10.LinearSRGBColorSpace;
this.renderer.domElement.className = "psv-canvas";
this.renderer.domElement.style.background = this.config.canvasBackground;
this.scene = new import_three10.Scene();
this.camera = new import_three10.PerspectiveCamera(50, 16 / 9, 0.1, 2 * SPHERE_RADIUS);
this.camera.matrixAutoUpdate = false;
const raycasterMesh = new import_three10.Mesh(
new import_three10.SphereGeometry(SPHERE_RADIUS).scale(-1, 1, 1),
new import_three10.MeshBasicMaterial({ opacity: 0, transparent: true, depthTest: false, depthWrite: false })
);
raycasterMesh.userData = { [VIEWER_DATA]: true };
this.scene.add(raycasterMesh);
this.raycaster = new import_three10.Raycaster();
this.frustum = new import_three10.Frustum();
this.container = document.createElement("div");
this.container.className = "psv-canvas-container";
this.container.appendChild(this.renderer.domElement);
this.viewer.container.appendChild(this.container);
this.viewer.addEventListener(SizeUpdatedEvent.type, this);
this.viewer.addEventListener(ZoomUpdatedEvent.type, this);
this.viewer.addEventListener(PositionUpdatedEvent.type, this);
this.viewer.addEventListener(RollUpdatedEvent.type, this);
this.viewer.addEventListener(ConfigChangedEvent.type, this);
this.hide();
}
get panoramaPose() {
return this.mesh.rotation;
}
get sphereCorrection() {
return this.meshContainer.rotation;
}
/**
* @internal
*/
init() {
this.show();
this.renderer.setAnimationLoop((t) => this.__renderLoop(t));
}
/**
* @internal
*/
destroy() {
this.renderer.setAnimationLoop(null);
this.cleanScene(this.scene);
this.renderer.dispose();
this.viewer.container.removeChild(this.container);
this.viewer.removeEventListener(SizeUpdatedEvent.type, this);
this.viewer.removeEventListener(ZoomUpdatedEvent.type, this);
this.viewer.removeEventListener(PositionUpdatedEvent.type, this);
this.viewer.removeEventListener(RollUpdatedEvent.type, this);
this.viewer.removeEventListener(ConfigChangedEvent.type, this);
super.destroy();
}
/**
* @internal
*/
handleEvent(e) {
switch (e.type) {
case SizeUpdatedEvent.type:
this.__onSizeUpdated();
break;
case ZoomUpdatedEvent.type:
this.__onZoomUpdated();
break;
case PositionUpdatedEvent.type:
this.__onPositionUpdated();
break;
case RollUpdatedEvent.type:
this.__onPositionUpdated();
break;
case ConfigChangedEvent.type:
if (e.containsOptions("fisheye")) {
this.__onPositionUpdated();
}
if (e.containsOptions("canvasBackground")) {
this.renderer.domElement.style.background = this.config.canvasBackground;
}
break;
}
}
/**
* Hides the viewer
*/
hide() {
this.container.style.opacity = "0";
}
/**
* Shows the viewer
*/
show() {
this.container.style.opacity = "1";
}
/**
* Resets or replaces the THREE renderer by a custom one
*/
setCustomRenderer(factory) {
if (factory) {
this.customRenderer = factory(this.renderer);
} else {
this.customRenderer = null;
}
this.viewer.needsUpdate();
}
/**
* Updates the size of the renderer and the aspect of the camera
*/
__onSizeUpdated() {
this.renderer.setSize(this.state.size.width, this.state.size.height);
this.camera.aspect = this.state.aspect;
this.camera.updateProjectionMatrix();
this.viewer.needsUpdate();
this.frustumNeedsUpdate = true;
}
/**
* Updates the fov of the camera
*/
__onZoomUpdated() {
this.camera.fov = this.state.vFov;
this.camera.updateProjectionMatrix();
this.viewer.needsUpdate();
this.frustumNeedsUpdate = true;
}
/**
* Updates the position of the camera
*/
__onPositionUpdated() {
this.camera.position.set(0, 0, 0);
this.camera.lookAt(this.state.direction);
if (this.config.fisheye) {
this.camera.position.copy(this.state.direction).multiplyScalar(this.config.fisheye / 2).negate();
}
this.camera.rotateZ(-this.state.roll);
this.camera.updateMatrix();
this.camera.updateMatrixWorld();
this.viewer.needsUpdate();
this.frustumNeedsUpdate = true;
}
/**
* Main event loop, performs a render if `state.needsUpdate` is true
*/
__renderLoop(timestamp) {
const elapsed = !this.timestamp ? 0 : timestamp - this.timestamp;
this.timestamp = timestamp;
this.viewer.dispatchEvent(new BeforeRenderEvent(timestamp, elapsed));
this.viewer.dynamics.update(elapsed);
if (this.state.needsUpdate || this.state.continuousUpdateCount > 0) {
this.state.needsUpdate = false;
(this.customRenderer || this.renderer).render(this.scene, this.camera);
this.viewer.dispatchEvent(new RenderEvent());
}
}
/**
* Applies the texture to the scene, creates the scene if needed
* @internal
*/
setTexture(textureData) {
if (!this.meshContainer) {
this.meshContainer = new import_three10.Group();
this.scene.add(this.meshContainer);
}
if (this.state.textureData) {
this.viewer.adapter.disposeTexture(this.state.textureData);
}
if (this.mesh) {
this.meshContainer.remove(this.mesh);
this.viewer.adapter.disposeMesh(this.mesh);
}
this.mesh = this.viewer.adapter.createMesh(textureData.panoData);
this.viewer.adapter.setTexture(this.mesh, textureData, false);
this.meshContainer.add(this.mesh);
this.state.textureData = textureData;
this.viewer.needsUpdate();
}
/**
* Applies a panorama data pose to a Mesh
* @internal
*/
setPanoramaPose(panoData, mesh = this.mesh) {
const cleanCorrection = this.viewer.dataHelper.cleanPanoramaPose(panoData);
const i = (cleanCorrection.pan ? 1 : 0) + (cleanCorrection.tilt ? 1 : 0) + (cleanCorrection.roll ? 1 : 0);
if (!Viewer.useNewAnglesOrder && i > 1) {
logWarn(`'panoData' Euler angles have changed in version 5.11.0.`);
logWarn(`Remove your 'useNewAnglesOrder' override to remove this warning (you might have to adapt your poseHeading/posePitch/poseRoll parameters).`);
}
if (Viewer.useNewAnglesOrder) {
mesh.rotation.set(cleanCorrection.tilt, cleanCorrection.pan, cleanCorrection.roll, "YXZ");
} else {
mesh.rotation.set(-cleanCorrection.tilt, -cleanCorrection.pan, -cleanCorrection.roll, "ZXY");
}
}
/**
* Applies a SphereCorrection to a Group
* @internal
*/
setSphereCorrection(sphereCorrection, group = this.meshContainer) {
const cleanCorrection = this.viewer.dataHelper.cleanSphereCorrection(sphereCorrection);
const i = (cleanCorrection.pan ? 1 : 0) + (cleanCorrection.tilt ? 1 : 0) + (cleanCorrection.roll ? 1 : 0);
if (!Viewer.useNewAnglesOrder && i > 1) {
logWarn(`'sphereCorrection' Euler angles have changed in version 5.11.0.`);
logWarn(`Remove your 'useNewAnglesOrder' override to remove this warning (you might have to adapt your poseHeading/posePitch/poseRoll parameters).`);
}
if (Viewer.useNewAnglesOrder) {
group.rotation.set(cleanCorrection.tilt, cleanCorrection.pan, cleanCorrection.roll, "YXZ");
} else {
group.rotation.set(cleanCorrection.tilt, cleanCorrection.pan, cleanCorrection.roll, "ZXY");
}
}
/**
* Performs transition between the current and a new texture
* @internal
*/
transition(textureData, options) {
const positionProvided = !isNil(options.position);
const zoomProvided = !isNil(options.zoom);
const e = new BeforeAnimateEvent(
positionProvided ? this.viewer.dataHelper.cleanPosition(options.position) : void 0,
options.zoom
);
this.viewer.dispatchEvent(e);
const meshContainer = new import_three10.Group();
const mesh = this.viewer.adapter.createMesh(textureData.panoData);
this.viewer.adapter.setTexture(mesh, textureData, true);
this.viewer.adapter.setTextureOpacity(mesh, 0);
this.setPanoramaPose(textureData.panoData, mesh);
this.setSphereCorrection(options.sphereCorrection, meshContainer);
if (positionProvided && options.transition === "fade-only") {
const currentPosition = this.viewer.getPosition();
const verticalAxis = new import_three10.Vector3(0, 1, 0);
meshContainer.rotateOnWorldAxis(verticalAxis, e.position.yaw - currentPosition.yaw);
const horizontalAxis = new import_three10.Vector3(0, 1, 0).cross(this.camera.getWorldDirection(new import_three10.Vector3())).normalize();
meshContainer.rotateOnWorldAxis(horizontalAxis, e.position.pitch - currentPosition.pitch);
}
meshContainer.add(mesh);
this.scene.add(meshContainer);
this.renderer.setRenderTarget(new import_three10.WebGLRenderTarget());
this.renderer.render(this.scene, this.camera);
this.renderer.setRenderTarget(null);
const { duration, properties } = this.viewer.dataHelper.getAnimationProperties(
options.speed,
options.transition === true ? e.position : null,
e.zoomLevel
);
const animation = new Animation({
properties: {
...properties,
opacity: { start: 0, end: 1 }
},
duration,
easing: "inOutCubic",
onTick: (props) => {
this.viewer.adapter.setTextureOpacity(mesh, props.opacity);
if (positionProvided && options.transition === true) {
this.viewer.dynamics.position.setValue({
yaw: props.yaw,
pitch: props.pitch
});
}
if (zoomProvided) {
this.viewer.dynamics.zoom.setValue(props.zoom);
}
this.viewer.needsUpdate();
}
});
animation.then((completed) => {
meshContainer.remove(mesh);
this.scene.remove(meshContainer);
if (completed) {
this.viewer.adapter.disposeTexture(this.state.textureData);
this.meshContainer.remove(this.mesh);
this.viewer.adapter.disposeMesh(this.mesh);
this.mesh = mesh;
this.meshContainer.add(mesh);
this.state.textureData = textureData;
this.setPanoramaPose(textureData.panoData);
this.setSphereCorrection(options.sphereCorrection);
if (positionProvided && options.transition === "fade-only") {
this.viewer.rotate(options.position);
}
} else {
this.viewer.adapter.disposeTexture(textureData);
this.viewer.adapter.disposeMesh(mesh);
}
});
return animation;
}
/**
* Returns intersections with objects in the scene
*/
getIntersections(viewerPoint) {
vector2.x = 2 * viewerPoint.x / this.state.size.width - 1;
vector2.y = -2 * viewerPoint.y / this.state.size.height + 1;
this.raycaster.setFromCamera(vector2, this.camera);
const intersections = this.raycaster.intersectObjects(this.scene.children, true).filter((i) => i.object.visible).filter((i) => i.object.isMesh && !!i.object.userData);
if (this.customRenderer?.getIntersections) {
intersections.push(...this.customRenderer.getIntersections(this.raycaster, vector2));
}
return intersections;
}
/**
* Checks if an object/point is currently visible
*/
isObjectVisible(value) {
if (!value) {
return false;
}
if (this.frustumNeedsUpdate) {
matrix4.multiplyMatrices(this.camera.projectionMatrix, this.camera.matrixWorldInverse);
this.frustum.setFromProjectionMatrix(matrix4);
this.frustumNeedsUpdate = false;
}
if (value.isVector3) {
return this.frustum.containsPoint(value);
} else if (value.isMesh && value.geometry) {
const mesh = value;
if (!mesh.geometry.boundingBox) {
mesh.geometry.computeBoundingBox();
}
box3.copy(mesh.geometry.boundingBox).applyMatrix4(mesh.matrixWorld);
return this.frustum.intersectsBox(box3);
} else if (value.isObject3D) {
return this.frustum.intersectsObject(value);
} else {
return false;
}
}
/**
* Adds an object to the THREE scene
*/
addObject(object) {
this.scene.add(object);
}
/**
* Removes an object from the THREE scene
*/
removeObject(object) {
this.scene.remove(object);
}
/**
* Calls `dispose` on all objects and textures
* @internal
*/
cleanScene(object) {
const disposeMaterial = (material) => {
material.map?.dispose();
if (material.uniforms) {
Object.values(material.uniforms).forEach((uniform) => {
uniform.value?.dispose?.();
});
}
material.dispose();
};
object.traverse((item) => {
item.geometry?.dispose();
if (item.material) {
if (Array.isArray(item.material)) {
item.material.forEach((material) => {
disposeMaterial(material);
});
} else {
disposeMaterial(item.material);
}
}
if (!(item instanceof import_three10.Scene)) {
item.dispose?.();
}
if (item !== object) {
this.cleanScene(item);
}
});
}
};
// src/lib/BlobLoader.ts
var import_three11 = require("three");
var BlobLoader = class extends import_three11.Loader {
// @ts-ignore
load(url, onLoad, onProgress, onError, abortSignal) {
const req = new Request(url, {
headers: new Headers(this.requestHeader),
credentials: this.withCredentials ? "include" : "same-origin"
});
fetch(req, {
signal: abortSignal
}).then((response) => {
if (response.status === 200 || response.status === 0) {
const reader = response.body.getReader();
const contentLength = response.headers.get("Content-Length") || response.headers.get("X-File-Size");
const total = contentLength ? parseInt(contentLength) : 0;
const lengthComputable = total !== 0;
let loaded = 0;
const stream = new ReadableStream({
start(controller) {
readData();
function readData() {
reader.read().then(({ done, value }) => {
if (done) {
controller.close();
} else {
loaded += value.byteLength;
const event = new ProgressEvent("progress", { lengthComputable, loaded, total });
onProgress(event);
controller.enqueue(value);
readData();
}
}).catch((err) => {
onError(err);
});
}
}
});
return new Response(stream);
} else {
throw new Error(`fetch for "${response.url}" responded with ${response.status}: ${response.statusText}`);
}
}).then((response) => {
return response.blob();
}).then((data) => {
onLoad(data);
}).catch((err) => {
onError(err);
});
}
};
// src/lib/ImageLoader.ts
var import_three12 = require("three");
var ImageLoader = class extends import_three12.Loader {
// @ts-ignore
load(url, onLoad, onError, abortSignal) {
const image = document.createElement("img");
function onImageLoad() {
removeEventListeners();
onLoad(this);
}
function onImageError(event) {
removeEventListeners();
if (abortSignal?.aborted) {
const e = new Error();
e.name = "AbortError";
e.message = "The operation was aborted.";
onError(e);
} else {
onError(event);
}
}
function onAbortSignal() {
image.src = "";
}
function removeEventListeners() {
image.removeEventListener("load", onImageLoad, false);
image.removeEventListener("error", onImageError, false);
abortSignal?.removeEventListener("abort", onAbortSignal, false);
}
image.addEventListener("load", onImageLoad, false);
image.addEventListener("error", onImageError, false);
abortSignal?.addEventListener("abort", onAbortSignal, false);
if (!url.startsWith("data:") && this.crossOrigin !== void 0) {
image.crossOrigin = this.crossOrigin;
}
image.src = url;
return image;
}
};
// src/services/TextureLoader.ts
var TextureLoader = class extends AbstractService {
/**
* @internal
*/
constructor(viewer) {
super(viewer);
this.abortCtrl = {};
this.fileLoader = new BlobLoader();
this.imageLoader = new ImageLoader();
if (this.config.withCredentials) {
this.fileLoader.setWithCredentials(true);
this.imageLoader.setCrossOrigin("use-credentials");
}
}
/**
* @internal
*/
destroy() {
this.abortLoading();
super.destroy();
}
/**
* Cancels current HTTP requests
* @internal
*/
abortLoading() {
Object.values(this.abortCtrl).forEach((ctrl) => ctrl.abort());
this.abortCtrl = {};
}
/**
* Loads a Blob with FileLoader
*/
loadFile(url, onProgress, cacheKey) {
const cached = Cache.get(url, cacheKey);
if (cached) {
if (cached instanceof Blob) {
onProgress?.(100);
return Promise.resolve(cached);
} else {
Cache.remove(url, cacheKey);
}
}
if (this.config.requestHeaders) {
this.fileLoader.setRequestHeader(this.config.requestHeaders(url));
}
return new Promise((resolve, reject) => {
let progress = 0;
onProgress?.(progress);
this.fileLoader.load(
url,
(result) => {
progress = 100;
onProgress?.(progress);
Cache.add(url, cacheKey, result);
resolve(result);
},
(e) => {
if (e.lengthComputable) {
const newProgress = e.loaded / e.total * 100;
if (newProgress > progress) {
progress = newProgress;
onProgress?.(progress);
}
}
},
(err) => {
reject(err);
},
this.__getAbortSignal(cacheKey)
);
});
}
/**
* Loads an image with ImageLoader or with FileLoader if progress is tracked or if request headers are configured
*/
loadImage(url, onProgress, cacheKey) {
const cached = Cache.get(url, cacheKey);
if (cached) {
onProgress?.(100);
if (cached instanceof Blob) {
return this.blobToImage(cached);
} else {
return Promise.resolve(cached);
}
}
if (!onProgress && !this.config.requestHeaders) {
return new Promise((resolve, reject) => {
this.imageLoader.load(
url,
(result) => {
Cache.add(url, cacheKey, result);
resolve(result);
},
(err) => {
reject(err);
},
this.__getAbortSignal(cacheKey)
);
});
} else {
return this.loadFile(url, onProgress, cacheKey).then((blob) => this.blobToImage(blob));
}
}
/**
* Converts a file loaded with {@link loadFile} into an image
*/
blobToImage(blob) {
return new Promise((resolve, reject) => {
const img = document.createElement("img");
img.onload = () => {
URL.revokeObjectURL(img.src);
resolve(img);
};
img.onerror = reject;
img.src = URL.createObjectURL(blob);
});
}
/**
* Preload a panorama file without displaying it
*/
preloadPanorama(panorama) {
if (this.viewer.adapter.supportsPreload(panorama)) {
return this.viewer.adapter.loadTexture(panorama, false);
} else {
return Promise.reject(new PSVError("Current adapter does not support preload"));
}
}
/**
* Get an abort signal
* the signal is shared accross all requests with the same cache key (for tiles adapters)
*/
__getAbortSignal(cacheKey) {
if (cacheKey) {
if (this.abortCtrl[cacheKey]?.signal.aborted) {
delete this.abortCtrl[cacheKey];
}
if (!this.abortCtrl[cacheKey]) {
this.abortCtrl[cacheKey] = new AbortController();
}
return this.abortCtrl[cacheKey].signal;
}
return null;
}
};
// src/services/ViewerDynamics.ts
var import_three13 = require("three");
var ViewerDynamics = class extends AbstractService {
/**
* @internal
*/
constructor(viewer) {
super(viewer);
this.zoom = new Dynamic(
(zoomLevel) => {
this.viewer.state.vFov = this.viewer.dataHelper.zoomLevelToFov(zoomLevel);
this.viewer.state.hFov = this.viewer.dataHelper.vFovToHFov(this.viewer.state.vFov);
this.viewer.dispatchEvent(new ZoomUpdatedEvent(zoomLevel));
},
{
defaultValue: this.viewer.config.defaultZoomLvl,
min: 0,
max: 100,
wrap: false
}
);
this.position = new MultiDynamic(
(position) => {
this.viewer.dataHelper.sphericalCoordsToVector3(position, this.viewer.state.direction);
this.viewer.dispatchEvent(new PositionUpdatedEvent(position));
},
{
yaw: new Dynamic(null, {
defaultValue: this.config.defaultYaw,
min: 0,
max: 2 * Math.PI,
wrap: true
}),
pitch: new Dynamic(null, {
defaultValue: this.config.defaultPitch,
min: -Math.PI / 2,
max: Math.PI / 2,
wrap: false
})
}
);
this.roll = new Dynamic(
(roll) => {
this.viewer.state.roll = roll;
this.viewer.dispatchEvent(new RollUpdatedEvent(roll));
},
{
defaultValue: 0,
min: -Math.PI,
max: Math.PI,
wrap: false
}
);
this.updateSpeeds();
}
/**
* @internal
*/
updateSpeeds() {
this.zoom.setSpeed(this.config.zoomSpeed * 50);
this.position.setSpeed(import_three13.MathUtils.degToRad(this.config.moveSpeed * 50));
this.roll.setSpeed(import_three13.MathUtils.degToRad(this.config.moveSpeed * 50));
}
/**
* @internal
*/
update(elapsed) {
this.zoom.update(elapsed);
this.position.update(elapsed);
this.roll.update(elapsed);
}
};
// src/services/ViewerState.ts
var import_three14 = require("three");
var ViewerState = class {
/**
* @internal
*/
// eslint-disable-next-line @typescript-eslint/no-empty-function
constructor() {
/**
* when all components are loaded
*/
this.ready = false;
/**
* if the view needs to be renderer
*/
this.needsUpdate = false;
/**
* number of plugins requesting to continuously render the scene
*/
this.continuousUpdateCount = 0;
/**
* if the keyboard events are currently listened to
*/
this.keyboardEnabled = false;
/**
* direction of the camera
*/
this.direction = new import_three14.Vector3(0, 0, SPHERE_RADIUS);
/**
* current camera roll
*/
this.roll = 0;
/**
* vertical FOV
*/
this.vFov = 60;
/**
* horizontal FOV
*/
this.hFov = 60;
/**
* renderer aspect ratio
*/
this.aspect = 1;
/**
* currently running animation
*/
this.animation = null;
/**
* currently running transition
*/
this.transitionAnimation = null;
/**
* promise of the last "setPanorama()" call
*/
this.loadingPromise = null;
/**
* time of the last user action
*/
this.idleTime = -1;
/**
* registered THREE objects observer
*/
this.objectsObservers = {};
/**
* size of the container
*/
this.size = {
width: 0,
height: 0
};
}
};
// src/Viewer.ts
var Viewer = class extends TypedEventTarget {
constructor(config) {
super();
/** @internal */
this.plugins = {};
/** @internal */
this.children = [];
this.onResize = throttle(() => this.navbar.autoSize(), 500);
this.parent = getElement(config.container);
if (!this.parent) {
throw new PSVError(`"container" element not found.`);
}
this.parent[VIEWER_DATA] = this;
this.container = document.createElement("div");
this.container.classList.add("psv-container");
this.parent.appendChild(this.container);
checkClosedShadowDom(this.parent);
checkStylesheet(this.container, "core");
this.state = new ViewerState();
this.config = getViewerConfig(config);
this.__setSize(this.config.size);
this.overlay = new Overlay(this);
try {
SYSTEM.load();
} catch (err) {
console.error(err);
this.showError(this.config.lang.webglError);
return;
}
Cache.init();
this.adapter = new this.config.adapter[0](this, this.config.adapter[1]);
this.renderer = new Renderer(this);
this.textureLoader = new TextureLoader(this);
this.eventsHandler = new EventsHandler(this);
this.dataHelper = new DataHelper(this);
this.dynamics = new ViewerDynamics(this);
this.adapter.init?.();
this.loader = new Loader(this);
this.navbar = new Navbar(this);
this.panel = new Panel(this);
this.notification = new Notification(this);
this.autoSize();
this.setCursor(null);
resolveBoolean(SYSTEM.isTouchEnabled, (enabled) => {
toggleClass(this.container, "psv--is-touch", enabled);
});
this.config.plugins.forEach(([plugin, opts]) => {
this.plugins[plugin.id] = new plugin(this, opts);
});
for (const plugin of Object.values(this.plugins)) {
plugin.init?.();
}
if (this.config.navbar) {
this.navbar.setButtons(this.config.navbar);
}
if (!this.state.loadingPromise) {
if (this.config.panorama) {
this.setPanorama(this.config.panorama);
} else {
this.loader.show();
}
}
}
/**
* Destroys the viewer
*/
destroy() {
this.stopAll();
this.stopKeyboardControl();
this.exitFullscreen();
for (const [id, plugin] of Object.entries(this.plugins)) {
plugin.destroy();
delete this.plugins[id];
}
this.children.slice().forEach((child) => child.destroy());
this.children.length = 0;
this.eventsHandler?.destroy();
this.renderer?.destroy();
this.textureLoader?.destroy();
this.dataHelper?.destroy();
this.adapter?.destroy();
this.dynamics?.destroy();
this.parent.removeChild(this.container);
delete this.parent[VIEWER_DATA];
}
init() {
this.eventsHandler.init();
this.renderer.init();
if (this.config.navbar) {
this.navbar.show();
}
if (this.config.keyboard === "always") {
this.startKeyboardControl();
}
this.resetIdleTimer();
this.state.ready = true;
this.dispatchEvent(new ReadyEvent());
}
/**
* Restarts the idle timer
* @internal
*/
resetIdleTimer() {
this.state.idleTime = performance.now();
}
/**
* Stops the idle timer
* @internal
*/
disableIdleTimer() {
this.state.idleTime = -1;
}
/**
* Returns the instance of a plugin if it exists
* @example By plugin identifier
* ```js
* viewer.getPlugin('markers')
* ```
* @example By plugin class with TypeScript support
* ```ts
* viewer.getPlugin(MarkersPlugin)
* ```
*/
getPlugin(pluginId) {
if (typeof pluginId === "string") {
return this.plugins[pluginId];
} else {
const pluginCtor = pluginInterop(pluginId);
return pluginCtor ? this.plugins[pluginCtor.id] : null;
}
}
/**
* Returns the current position of the camera
*/
getPosition() {
return this.dataHelper.cleanPosition(this.dynamics.position.current);
}
/**
* Returns the current zoom level
*/
getZoomLevel() {
return this.dynamics.zoom.current;
}
/**
* Returns the current viewer size
*/
getSize() {
return { ...this.state.size };
}
/**
* Checks if the viewer is in fullscreen
*/
isFullscreenEnabled() {
return isFullscreenEnabled(this.parent, SYSTEM.isIphone);
}
/**
* Request a new render of the scene
*/
needsUpdate() {
this.state.needsUpdate = true;
}
/**
* Request the scene to be continuously renderer (when using videos)
*/
needsContinuousUpdate(enabled) {
if (enabled) {
this.state.continuousUpdateCount++;
} else if (this.state.continuousUpdateCount > 0) {
this.state.continuousUpdateCount--;
}
}
/**
* Resizes the scene if the viewer is resized
*/
autoSize() {
if (this.container.clientWidth !== this.state.size.width || this.container.clientHeight !== this.state.size.height) {
this.state.size.width = Math.round(this.container.clientWidth);
this.state.size.height = Math.round(this.container.clientHeight);
this.state.aspect = this.state.size.width / this.state.size.height;
this.state.hFov = this.dataHelper.vFovToHFov(this.state.vFov);
this.dispatchEvent(new SizeUpdatedEvent(this.getSize()));
this.onResize();
}
}
/**
* Loads a new panorama file
* Loads a new panorama file, optionally changing the camera position/zoom and activating the transition animation.
* If the "options" parameter is not defined, the camera will not move and the ongoing animation will continue.
* If another loading is already in progress it will be aborted.
* @returns promise resolved with false if the loading was aborted by another call
*/
setPanorama(path, options = {}) {
this.textureLoader.abortLoading();
this.state.transitionAnimation?.cancel();
if (!this.state.ready) {
["sphereCorrection", "panoData"].forEach((opt) => {
if (!(opt in options)) {
options[opt] = this.config[opt];
}
});
}
if (options.transition === void 0) {
options.transition = true;
}
if (options.speed === void 0) {
options.speed = DEFAULT_TRANSITION;
}
if (options.showLoader === void 0) {
options.showLoader = true;
}
if (options.caption === void 0) {
options.caption = this.config.caption;
}
if (options.description === void 0) {
options.description = this.config.description;
}
if (!options.panoData && typeof this.config.panoData === "function") {
options.panoData = this.config.panoData;
}
this.hideError();
this.resetIdleTimer();
this.config.panorama = path;
this.config.caption = options.caption;
this.config.description = options.description;
const done = (err) => {
if (isAbortError(err)) {
return false;
}
this.loader.hide();
this.state.loadingPromise = null;
if (err) {
this.navbar.setCaption("");
this.showError(this.config.lang.loadError);
console.error(err);
this.dispatchEvent(new PanoramaErrorEvent(path, err));
throw err;
} else {
this.navbar.setCaption(this.config.caption);
return true;
}
};
this.navbar.setCaption(`${this.config.lang.loading}`);
if (options.showLoader || !this.state.ready) {
this.loader.show();
}
this.dispatchEvent(new PanoramaLoadEvent(path));
const loadingPromise = this.adapter.loadTexture(this.config.panorama, true, options.panoData).then((textureData) => {
if (textureData.panorama !== this.config.panorama) {
this.adapter.disposeTexture(textureData);
throw getAbortError();
}
const cleanOptions = this.dataHelper.cleanPanoramaOptions(options, textureData.panoData);
if (!isNil(cleanOptions.zoom) || !isNil(cleanOptions.position)) {
this.stopAll();
}
return {
textureData,
cleanOptions
};
});
if (!options.transition || !this.state.ready || !this.adapter.supportsTransition(this.config.panorama)) {
this.state.loadingPromise = loadingPromise.then(({ textureData, cleanOptions }) => {
this.renderer.show();
this.renderer.setTexture(textureData);
this.renderer.setPanoramaPose(textureData.panoData);
this.renderer.setSphereCorrection(options.sphereCorrection);
if (!this.state.ready) {
this.init();
}
this.dispatchEvent(new PanoramaLoadedEvent(textureData));
if (!isNil(cleanOptions.zoom)) {
this.zoom(cleanOptions.zoom);
}
if (!isNil(cleanOptions.position)) {
this.rotate(cleanOptions.position);
}
}).then(
() => done(),
(err) => done(err)
);
} else {
this.state.loadingPromise = loadingPromise.then(({ textureData, cleanOptions }) => {
this.loader.hide();
this.dispatchEvent(new PanoramaLoadedEvent(textureData));
this.state.transitionAnimation = this.renderer.transition(textureData, cleanOptions);
return this.state.transitionAnimation;
}).then((completed) => {
this.state.transitionAnimation = null;
this.dispatchEvent(new TransitionDoneEvent(completed));
if (!completed) {
throw getAbortError();
}
}).then(
() => done(),
(err) => done(err)
);
}
return this.state.loadingPromise;
}
/**
* Update options
* @throws {@link PSVError} if the configuration is invalid
*/
setOptions(options) {
const rawConfig = {
...this.config,
...options
};
for (let [key, value] of Object.entries(options)) {
if (!(key in DEFAULTS)) {
logWarn(`Unknown option ${key}`);
continue;
}
if (key in READONLY_OPTIONS) {
logWarn(READONLY_OPTIONS[key]);
continue;
}
if (key in CONFIG_PARSERS) {
value = CONFIG_PARSERS[key](value, {
rawConfig,
defValue: DEFAULTS[key]
});
}
this.config[key] = value;
switch (key) {
case "mousemove":
if (!this.state.cursorOverride) {
this.setCursor(null);
}
break;
case "caption":
this.navbar.setCaption(this.config.caption);
break;
case "size":
this.resize(this.config.size);
break;
case "sphereCorrection":
this.renderer.setSphereCorrection(this.config.sphereCorrection);
break;
case "navbar":
case "lang":
this.navbar.setButtons(this.config.navbar);
break;
case "moveSpeed":
case "zoomSpeed":
this.dynamics.updateSpeeds();
break;
case "minFov":
case "maxFov":
this.dynamics.zoom.setValue(this.dataHelper.fovToZoomLevel(this.state.vFov));
this.dispatchEvent(new ZoomUpdatedEvent(this.getZoomLevel()));
break;
case "keyboard":
if (this.config.keyboard === "always") {
this.startKeyboardControl();
} else {
this.stopKeyboardControl();
}
break;
default:
break;
}
}
this.needsUpdate();
this.dispatchEvent(new ConfigChangedEvent(Object.keys(options)));
}
/**
* Update options
* @throws {@link PSVError} if the configuration is invalid
*/
setOption(option, value) {
this.setOptions({ [option]: value });
}
/**
* Displays an error message over the viewer
*/
showError(message) {
this.overlay.show({
id: IDS.ERROR,
image: error_default,
title: message,
dissmisable: false
});
}
/**
* Hides the error message
*/
hideError() {
this.overlay.hide(IDS.ERROR);
}
/**
* Rotates the view to specific position
*/
rotate(position) {
const e = new BeforeRotateEvent(this.dataHelper.cleanPosition(position));
this.dispatchEvent(e);
if (e.defaultPrevented) {
return;
}
this.dynamics.position.setValue(e.position);
}
/**
* Zooms to a specific level between `maxFov` and `minFov`
*/
zoom(level) {
this.dynamics.zoom.setValue(level);
}
/**
* Increases the zoom level
*/
zoomIn(step = 1) {
this.dynamics.zoom.step(step);
}
/**
* Decreases the zoom level
*/
zoomOut(step = 1) {
this.dynamics.zoom.step(-step);
}
/**
* Rotates and zooms the view with a smooth animation
*/
animate(options) {
const positionProvided = isExtendedPosition(options);
const zoomProvided = !isNil(options.zoom);
const e = new BeforeAnimateEvent(
positionProvided ? this.dataHelper.cleanPosition(options) : void 0,
options.zoom
);
this.dispatchEvent(e);
if (e.defaultPrevented) {
return;
}
this.stopAll();
const { duration, properties } = this.dataHelper.getAnimationProperties(options.speed, e.position, e.zoomLevel);
if (!duration) {
if (positionProvided) {
this.rotate(e.position);
}
if (zoomProvided) {
this.zoom(e.zoomLevel);
}
return new Animation(null);
}
this.state.animation = new Animation({
properties,
duration,
easing: options.easing || "inOutSine",
onTick: (props) => {
if (positionProvided) {
this.dynamics.position.setValue({
yaw: props.yaw,
pitch: props.pitch
});
}
if (zoomProvided) {
this.dynamics.zoom.setValue(props.zoom);
}
}
});
this.state.animation.then(() => {
this.state.animation = null;
this.resetIdleTimer();
});
return this.state.animation;
}
/**
* Stops the ongoing animation
* The return value is a Promise because the is no guaranty the animation can be stopped synchronously.
*/
stopAnimation() {
if (this.state.animation) {
this.state.animation.cancel();
return this.state.animation;
} else {
return Promise.resolve();
}
}
/**
* Resizes the viewer
*/
resize(size) {
this.__setSize(size);
this.autoSize();
}
__setSize(size) {
const s = size;
["width", "height"].forEach((dim) => {
if (size && s[dim]) {
if (/^[0-9.]+$/.test(s[dim])) {
s[dim] += "px";
}
this.parent.style[dim] = s[dim];
}
});
}
/**
* Enters the fullscreen mode
*/
enterFullscreen() {
if (!this.isFullscreenEnabled()) {
requestFullscreen(this.parent, SYSTEM.isIphone);
}
}
/**
* Exits the fullscreen mode
*/
exitFullscreen() {
if (this.isFullscreenEnabled()) {
exitFullscreen(SYSTEM.isIphone);
}
}
/**
* Enters or exits the fullscreen mode
*/
toggleFullscreen() {
if (!this.isFullscreenEnabled()) {
this.enterFullscreen();
} else {
this.exitFullscreen();
}
}
/**
* Enables the keyboard controls
*/
startKeyboardControl() {
this.state.keyboardEnabled = true;
}
/**
* Disables the keyboard controls
*/
stopKeyboardControl() {
this.state.keyboardEnabled = false;
}
/**
* Creates a new tooltip
* Use {@link Tooltip.move} to update the tooltip without re-create
* @throws {@link PSVError} if the configuration is invalid
*/
createTooltip(config) {
return new Tooltip(this, config);
}
/**
* Changes the global mouse cursor
*/
setCursor(cursor) {
this.state.cursorOverride = cursor;
if (!cursor) {
this.container.style.cursor = this.config.mousemove ? "move" : "default";
} else {
this.container.style.cursor = cursor;
}
}
/**
* Subscribes to events on objects in the three.js scene
* @param userDataKey - only objects with the following `userData` will be observed
*/
observeObjects(userDataKey) {
if (!this.state.objectsObservers[userDataKey]) {
this.state.objectsObservers[userDataKey] = null;
}
}
/**
* Unsubscribes to events on objects
*/
unobserveObjects(userDataKey) {
delete this.state.objectsObservers[userDataKey];
}
/**
* Stops all current animations
* @internal
*/
stopAll() {
this.dispatchEvent(new StopAllEvent());
this.disableIdleTimer();
return this.stopAnimation();
}
};
/**
* Change the order in which the panoData and sphereCorrection angles are applied from 'ZXY' to 'YXZ'
* @deprecated Will be removed in version 5.12
*/
Viewer.useNewAnglesOrder = true;
// src/index.ts
var VERSION = "5.11.1";
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
AbstractAdapter,
AbstractButton,
AbstractComponent,
AbstractConfigurablePlugin,
AbstractPlugin,
CONSTANTS,
Cache,
DEFAULTS,
DualFisheyeAdapter,
EquirectangularAdapter,
PSVError,
SYSTEM,
TypedEvent,
VERSION,
Viewer,
events,
registerButton,
utils
});
//# sourceMappingURL=index.cjs.map