// Global variables to ease access
let viewer2d = null;
let gui = null;
const modelId = "my-model-id";
// Enum of ids for HTML elements referenced in this file
const ElementId = {
CONSOLE_COPY: "console-copy",
CONSOLE_TEXT: "console-text",
ERROR: "error",
GUI: "gui",
LOADING_ICON: "loading-icon",
PLAY_BUTTON: "play-button",
PLAY_ICON: "play-icon",
SPLASH: "splash",
TOOLTIP: "tooltip",
VIEWER_2D: "viewer-2d"
};
// SVG icon for markers
const markerIcon =
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='_size_' height='_size_' viewBox='0 0 38 38'%3E%3Cdefs%3E%3Cfilter id='prefix__a' width='169.2%25' height='169.2%25' x='-34.6%25' y='-26.9%25' filterUnits='objectBoundingBox'%3E%3CfeMorphology in='SourceAlpha' operator='dilate' radius='2' result='shadowSpreadOuter1'/%3E%3CfeOffset dy='2' in='shadowSpreadOuter1' result='shadowOffsetOuter1'/%3E%3CfeGaussianBlur in='shadowOffsetOuter1' result='shadowBlurOuter1' stdDeviation='2'/%3E%3CfeComposite in='shadowBlurOuter1' in2='SourceAlpha' operator='out' result='shadowBlurOuter1'/%3E%3CfeColorMatrix in='shadowBlurOuter1' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.28 0'/%3E%3C/filter%3E%3Ccircle id='prefix__b' cx='250' cy='315' r='13'/%3E%3C/defs%3E%3Cg fill-rule='evenodd' stroke-linecap='round' stroke-linejoin='round' transform='translate(-231 -298)'%3E%3Cuse filter='url(%23prefix__a)' xlink:href='%23prefix__b'/%3E%3Cuse fill='%23000' stroke='%23FFF' stroke-width='4' xlink:href='%23prefix__b'/%3E%3C/g%3E%3C/svg%3E%0A";
// Enum of style values used in this file
const Style = {
FLEX: "flex",
NONE: "none"
};
// Reset tooltip text
const consoleCopy = document.getElementById(ElementId.CONSOLE_COPY);
const onCopyLeave = function () {
document.getElementById(ElementId.TOOLTIP).innerHTML = "Copy";
};
consoleCopy.addEventListener("mouseleave", onCopyLeave);
consoleCopy.addEventListener("touchend", onCopyLeave);
/**
* Catenda 2D viewer Event Example.
* The runExample function contains the 2D viewer related calls to fulfill the example shown.
*/
// The code specific to the example
const runExample = function () {
// gui labels
const CLICK_EVENT = "Click Event Handler";
const CONTEXT_MENU_EVENT = "Context Menu Event Handler";
const DOUBLE_CLICK_EVENT = "Double Click Event Handler";
const MARKER_CLICK_EVENT = "Marker Click Event Handler";
const MARKER_DRAG_EVENT = "Marker Drag Event Handler";
const MARKER_DRAG_END_EVENT = "Marker Drag End Event Handler";
const MARKER_HOVER_EVENT = "Marker Hover Event Handler";
const SELECT_EVENT = "Select Event Handler";
const VIEWPOINT_EVENT = "Viewpoint Event Handler";
const REMOVE_ALL_EVENTS = "Remove All Event Handlers";
// Keep track of added marker to ease removal
let markerId = null;
/**
* Start off by showing a storey
*/
const storeys = viewer2d.getStoreys();
if (storeys.length > 0) {
viewer2d.showStorey(storeys[1].id);
}
viewer2d.setViewport("fit", { expandInPercentage: 15 });
/**
* Store reference returned from registering an event handler that includes remove method
*/
const eventHandlers = {};
const addMarker = () => {
if (!markerId) {
const markerSize = 32;
const marker = {
color: "#004d47",
clickable: true,
draggable: true,
icon: {
anchor: {
x: markerSize / 2,
y: markerSize / 2
},
size: {
height: markerSize,
width: markerSize
},
url: markerIcon.replace(/_size_/g, markerSize)
},
x: 0,
y: 0
};
viewer2d.clearMarkers();
markerId = viewer2d.addMarker(marker);
}
};
/**
* Map on screen buttons to viewer behaviour
*/
const onClickEventHandlerChange = function (isEnabled) {
if (isEnabled) {
const onClick = function (event) {
setOutput(event);
};
const handler = viewer2d.addEventListener("viewer2d.click", onClick);
eventHandlers[CLICK_EVENT] = handler;
disableGuiProperty(SELECT_EVENT);
enableGuiProperty(REMOVE_ALL_EVENTS);
} else {
eventHandlers[CLICK_EVENT].remove();
delete eventHandlers[CLICK_EVENT];
enableGuiProperty(SELECT_EVENT);
setOutput("");
}
};
const onContextMenuEventHandlerChange = function (isEnabled) {
if (isEnabled) {
const onContextMenu = function (event) {
setOutput(event);
};
const handler = viewer2d.addEventListener(
"viewer2d.contextmenu",
onContextMenu
);
eventHandlers[CONTEXT_MENU_EVENT] = handler;
enableGuiProperty(REMOVE_ALL_EVENTS);
} else {
eventHandlers[CONTEXT_MENU_EVENT].remove();
delete eventHandlers[CONTEXT_MENU_EVENT];
setOutput("");
}
};
const onDoubleClickEventHandlerChange = function (isEnabled) {
if (isEnabled) {
const onDoubleClick = function (event) {
setOutput(event);
};
const handler = viewer2d.addEventListener(
"viewer2d.dblclick",
onDoubleClick
);
eventHandlers[DOUBLE_CLICK_EVENT] = handler;
enableGuiProperty(REMOVE_ALL_EVENTS);
} else {
eventHandlers[DOUBLE_CLICK_EVENT].remove();
delete eventHandlers[DOUBLE_CLICK_EVENT];
setOutput("");
}
};
const onMarkerClickEventHandlerChange = function (isEnabled) {
if (isEnabled) {
const onMarkerClick = function (event) {
setOutput(event);
};
const handler = viewer2d.addEventListener(
"viewer2d.markerclick",
onMarkerClick
);
eventHandlers[MARKER_CLICK_EVENT] = handler;
enableGuiProperty(REMOVE_ALL_EVENTS);
addMarker();
} else {
eventHandlers[MARKER_CLICK_EVENT].remove();
delete eventHandlers[MARKER_CLICK_EVENT];
if (
!eventHandlers[MARKER_DRAG_EVENT] &&
!eventHandlers[MARKER_DRAG_END_EVENT] &&
!eventHandlers[MARKER_HOVER_EVENT]
) {
viewer2d.removeMarker(markerId);
markerId = null;
}
setOutput("");
}
};
const onMarkerDragEventHandlerChange = function (isEnabled) {
if (isEnabled) {
const onMarkerDrag = function (event) {
setOutput(event);
};
const handler = viewer2d.addEventListener(
"viewer2d.markerdrag",
onMarkerDrag
);
eventHandlers[MARKER_DRAG_EVENT] = handler;
enableGuiProperty(REMOVE_ALL_EVENTS);
addMarker();
} else {
eventHandlers[MARKER_DRAG_EVENT].remove();
delete eventHandlers[MARKER_DRAG_EVENT];
if (
!eventHandlers[MARKER_CLICK_EVENT] &&
!eventHandlers[MARKER_DRAG_END_EVENT] &&
!eventHandlers[MARKER_HOVER_EVENT]
) {
viewer2d.removeMarker(markerId);
markerId = null;
}
setOutput("");
}
};
const onMarkerDragEndEventHandlerChange = function (isEnabled) {
if (isEnabled) {
const onMarkerDragComplete = function (event) {
setOutput(event);
};
const handler = viewer2d.addEventListener(
"viewer2d.markerdragend",
onMarkerDragComplete
);
eventHandlers[MARKER_DRAG_END_EVENT] = handler;
enableGuiProperty(REMOVE_ALL_EVENTS);
addMarker();
} else {
eventHandlers[MARKER_DRAG_END_EVENT].remove();
delete eventHandlers[MARKER_DRAG_END_EVENT];
if (
!eventHandlers[MARKER_CLICK_EVENT] &&
!eventHandlers[MARKER_DRAG_EVENT] &&
!eventHandlers[MARKER_HOVER_EVENT]
) {
viewer2d.removeMarker(markerId);
markerId = null;
}
setOutput("");
}
};
const onMarkerHoverEventHandlerChange = function (isEnabled) {
if (isEnabled) {
const onMarkerHover = function (event) {
setOutput(event);
};
const handler = viewer2d.addEventListener(
"viewer2d.markerhover",
onMarkerHover
);
eventHandlers[MARKER_HOVER_EVENT] = handler;
enableGuiProperty(REMOVE_ALL_EVENTS);
addMarker();
} else {
eventHandlers[MARKER_HOVER_EVENT].remove();
delete eventHandlers[MARKER_HOVER_EVENT];
if (
!eventHandlers[MARKER_CLICK_EVENT] &&
!eventHandlers[MARKER_DRAG_EVENT] &&
!eventHandlers[MARKER_DRAG_END_EVENT]
) {
viewer2d.removeMarker(markerId);
markerId = null;
}
setOutput("");
}
};
const onSelectEventHandlerChange = function (isEnabled) {
if (isEnabled) {
const onSelect = function (event) {
setOutput(event);
};
const handler = viewer2d.addEventListener("viewer2d.select", onSelect);
eventHandlers[SELECT_EVENT] = handler;
disableGuiProperty(CLICK_EVENT);
enableGuiProperty(REMOVE_ALL_EVENTS);
} else {
eventHandlers[SELECT_EVENT].remove();
delete eventHandlers[SELECT_EVENT];
enableGuiProperty(CLICK_EVENT);
setOutput("");
}
};
const onViewpointEventHandlerChange = function (isEnabled) {
if (isEnabled) {
const onViewpointChange = function (event) {
setOutput(event);
};
const handler = viewer2d.addEventListener(
"viewer2d.viewpoint",
onViewpointChange
);
eventHandlers[VIEWPOINT_EVENT] = handler;
enableGuiProperty(REMOVE_ALL_EVENTS);
const viewpoint = {
direction: 90,
location: {
x: 1,
y: 1
}
};
viewer2d.setViewpoint(viewpoint);
viewer2d.showViewpoint();
} else {
eventHandlers[VIEWPOINT_EVENT].remove();
delete eventHandlers[VIEWPOINT_EVENT];
viewer2d.hideViewpoint();
setOutput("");
}
};
const onRemoveAllEventHandlersClick = function () {
Object.values(eventHandlers).forEach((eventHandler) =>
eventHandler.remove()
);
gui.controllers.forEach((controller) => controller.enable());
disableGuiProperty(REMOVE_ALL_EVENTS);
Object.keys(state).forEach((key) => {
if (key !== REMOVE_ALL_EVENTS) {
console.log(key);
updateGuiPropertyValue(key, false);
}
});
};
/**
* Map of gui items to data and callbacks
*/
const state = {
[CLICK_EVENT]: false,
[CONTEXT_MENU_EVENT]: false,
[DOUBLE_CLICK_EVENT]: false,
[MARKER_CLICK_EVENT]: false,
[MARKER_DRAG_EVENT]: false,
[MARKER_DRAG_END_EVENT]: false,
[MARKER_HOVER_EVENT]: false,
[SELECT_EVENT]: false,
[VIEWPOINT_EVENT]: false,
[REMOVE_ALL_EVENTS]: onRemoveAllEventHandlersClick
};
/**
* Build gui panel
*/
gui.add(state, CLICK_EVENT).onChange(onClickEventHandlerChange);
gui.add(state, CONTEXT_MENU_EVENT).onChange(onContextMenuEventHandlerChange);
gui.add(state, DOUBLE_CLICK_EVENT).onChange(onDoubleClickEventHandlerChange);
gui.add(state, MARKER_CLICK_EVENT).onChange(onMarkerClickEventHandlerChange);
gui.add(state, MARKER_DRAG_EVENT).onChange(onMarkerDragEventHandlerChange);
gui
.add(state, MARKER_DRAG_END_EVENT)
.onChange(onMarkerDragEndEventHandlerChange);
gui.add(state, MARKER_HOVER_EVENT).onChange(onMarkerHoverEventHandlerChange);
gui.add(state, SELECT_EVENT).onChange(onSelectEventHandlerChange);
gui.add(state, VIEWPOINT_EVENT).onChange(onViewpointEventHandlerChange);
gui.add(state, REMOVE_ALL_EVENTS).disable();
};
/**
* Code required for bootstrapping a 2D viewer instance with which to execute the example
*/
// Bootstrap a 2D viewer instance and load a model
const initialize = async function (onViewerReady) {
// Show the loading indicator until the 2D viewer is ready
showLoadingIndicator();
// Hide any previous error message
hideElement(ElementId.ERROR);
// Create a gui instance for 2D viewer controls
gui = new lil.GUI({
container: document.getElementById("gui")
});
gui.title("Options");
// HTML Div element where the 2D viewer is mounted
const div2d = document.getElementById(ElementId.VIEWER_2D);
// Options used when creating a 2D viewer instance
const viewerOptions = {
hoverSpaces: true,
selectColor: "#ade0bc",
showViewpoint: false
};
// Create a 2D viewer instance
viewer2d = new bimsync.viewer2d.Viewer2D(div2d, viewerOptions);
// Options used when loading the model
const loadModelOptions = {
modelId: modelId,
modelName: "My Model Name"
};
// Fetch a 2D viewer token for the model to load
const url2d = await getToken();
// Load the model
viewer2d
.loadUrl(url2d, loadModelOptions)
.then(function () {
// 2D viewer is ready, stop loading
showPlayButton();
// Hide the splash screen
hideElement(ElementId.SPLASH);
// Show the 2D viewer controls
showElement(ElementId.GUI);
// 2D viewer is ready, run the callback
onViewerReady();
})
.catch(function (error) {
showError();
});
};
// Entry point called when user clicks the play button
const start = function () {
// Bootstrap a 2D viewer instance then run the example
initialize(runExample);
};
/**
* Utility functions used in this file
*/
// Set console text in clipboard
const copyConsole = async function () {
await navigator.clipboard.writeText(
document.getElementById(ElementId.CONSOLE_TEXT).innerHTML
);
document.getElementById(ElementId.TOOLTIP).innerHTML = "Copied!";
};
// Disable a property in the gui panel
const disableGuiProperty = function (propertyName) {
gui
.controllersRecursive()
.find(({ property }) => property === propertyName)
.disable();
};
// Enable a property in the gui panel
const enableGuiProperty = function (propertyName) {
gui
.controllersRecursive()
.find(({ property }) => property === propertyName)
.enable();
};
// Retrieve a 2D viewer token
const getToken = async function () {
/**
* Fetch a 2D viewer token from the Catenda API
* Here we are using a CodePen specific endpoint for demo purposes
* Replace this with your own token endpoint in a real application
* Find out more at https://developers.catenda.com/viewer-2d/create-2d-viewer-token
* The model this token provides access to is licensed under the Creative Commons Attribution 4.0 International License.
* (C) original authors
* https://github.com/buildingSMART/Sample-Test-Files
* More info and a link to the full license text is available on http://creativecommons.org/licenses/by/4.0/
*/
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const tryCount = 4;
let response, result;
// Try to fetch the token a few times before giving up
for (let i = 1; i <= tryCount; i++) {
response = await fetch(
"https://documentation-edge.developers.catenda.com/token2d"
);
if (response.ok) {
break;
} else {
// If the response is not ok, we wait a bit before trying again
if (i < tryCount) {
await delay(i * 1000);
}
continue;
}
}
// If the response is ok, we can proceed
if (response && response.ok) {
const data = await response.json();
result = data.url;
} else {
showError();
}
return result;
};
// Hide an HTML element passing its id
const hideElement = function (elementId) {
document.getElementById(elementId).style.display = Style.NONE;
};
// Set the text displayed in the output panel
const setOutput = function (value) {
const text = value instanceof Object ? JSON.stringify(value) : value;
document.getElementById(ElementId.CONSOLE_TEXT).innerHTML = text;
};
// Show an HTML element passing its id
const showElement = function (elementId) {
document.getElementById(elementId).style.display = Style.FLEX;
};
// Reset after an error
const showError = function () {
viewer2d.dispose();
gui.destroy();
showPlayButton();
showElement(ElementId.ERROR);
};
// Display the loading indicator
const showLoadingIndicator = function () {
const playButton = document.getElementById(ElementId.PLAY_BUTTON);
playButton.disabled = true;
const playIconElement = document.getElementById(ElementId.PLAY_ICON);
let loadingIcon = document.createElement("i");
loadingIcon.id = ElementId.LOADING_ICON;
loadingIcon.classList.add("fa", "fa-spinner", "fa-spin");
playIconElement.replaceWith(loadingIcon);
};
// Display the play button
const showPlayButton = function () {
const playButton = document.getElementById(ElementId.PLAY_BUTTON);
playButton.disabled = false;
const loadingIconElement = document.getElementById(ElementId.LOADING_ICON);
let playIcon = document.createElement("i");
playIcon.id = ElementId.PLAY_ICON;
playIcon.classList.add("fa", "fa-play");
loadingIconElement.replaceWith(playIcon);
};
// Update a value used in the gui panel to reflect a change
const updateGuiPropertyValue = function (propertyName, value) {
gui
.controllersRecursive()
.find(({ property }) => property === propertyName)
.setValue(value);
};